@bevel-software/platform-core-backend 0.35.0 → 0.36.1

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 (827) hide show
  1. package/agent-guide/directory-structure.md +1 -1
  2. package/agent-guide/onboarding.md +111 -0
  3. package/agent-guide/start-here.md +1 -0
  4. package/agent-guide/tool-manuals.md +1 -0
  5. package/dist/core/catalog-cache-invalidation.d.ts +2 -2
  6. package/dist/core/catalog-cache-invalidation.d.ts.map +1 -1
  7. package/dist/core/compose-embed.d.ts +2 -2
  8. package/dist/core/compose-embed.d.ts.map +1 -1
  9. package/dist/core/compose-embed.js +1 -1
  10. package/dist/core/compose-embed.js.map +1 -1
  11. package/dist/core/core-ports.d.ts +19 -4
  12. package/dist/core/core-ports.d.ts.map +1 -1
  13. package/dist/core/core-ports.js.map +1 -1
  14. package/dist/core/create-core-server.d.ts.map +1 -1
  15. package/dist/core/create-core-server.js +26 -24
  16. package/dist/core/create-core-server.js.map +1 -1
  17. package/dist/core/create-core-services.d.ts +19 -13
  18. package/dist/core/create-core-services.d.ts.map +1 -1
  19. package/dist/core/create-core-services.js +68 -56
  20. package/dist/core/create-core-services.js.map +1 -1
  21. package/dist/core/lifecycle.d.ts +4 -4
  22. package/dist/core/lifecycle.d.ts.map +1 -1
  23. package/dist/core/lifecycle.js +5 -5
  24. package/dist/core/lifecycle.js.map +1 -1
  25. package/dist/core-config.d.ts +2 -2
  26. package/dist/core-config.js +3 -3
  27. package/dist/core-config.js.map +1 -1
  28. package/dist/index.d.ts +4 -4
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +3 -3
  31. package/dist/index.js.map +1 -1
  32. package/dist/modules/access/access-control.service.js +1 -1
  33. package/dist/modules/access/access-control.service.js.map +1 -1
  34. package/dist/modules/access/access-declarations.d.ts +2 -2
  35. package/dist/modules/access/access-declarations.d.ts.map +1 -1
  36. package/dist/modules/access/access-declarations.js +2 -2
  37. package/dist/modules/access/access-declarations.js.map +1 -1
  38. package/dist/modules/access/access-mutation.service.d.ts +2 -2
  39. package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
  40. package/dist/modules/access/access-mutation.service.js +2 -2
  41. package/dist/modules/access/access-mutation.service.js.map +1 -1
  42. package/dist/modules/access/access-requests.routes.d.ts +2 -2
  43. package/dist/modules/access/access-requests.routes.d.ts.map +1 -1
  44. package/dist/modules/access/access-requests.routes.js +5 -5
  45. package/dist/modules/access/access-requests.routes.js.map +1 -1
  46. package/dist/modules/access/access-requests.service.d.ts +2 -2
  47. package/dist/modules/access/access-requests.service.d.ts.map +1 -1
  48. package/dist/modules/access/access-requests.service.js +11 -11
  49. package/dist/modules/access/access-requests.service.js.map +1 -1
  50. package/dist/modules/access/access.routes.d.ts +2 -2
  51. package/dist/modules/access/access.routes.d.ts.map +1 -1
  52. package/dist/modules/access/access.routes.js +9 -9
  53. package/dist/modules/access/access.routes.js.map +1 -1
  54. package/dist/modules/access/admin-locked-commit.d.ts +5 -5
  55. package/dist/modules/access/admin-locked-commit.d.ts.map +1 -1
  56. package/dist/modules/access/admin-locked-commit.js +7 -7
  57. package/dist/modules/access/admin-locked-commit.js.map +1 -1
  58. package/dist/modules/access/admin-route-helpers.d.ts +1 -1
  59. package/dist/modules/access/admin-route-helpers.js +5 -5
  60. package/dist/modules/access/admin-route-helpers.js.map +1 -1
  61. package/dist/modules/access/change-read-gate.d.ts +1 -1
  62. package/dist/modules/access/change-read-gate.js +1 -1
  63. package/dist/modules/access/groups-admin.service.d.ts +7 -7
  64. package/dist/modules/access/groups-admin.service.d.ts.map +1 -1
  65. package/dist/modules/access/groups-admin.service.js +6 -6
  66. package/dist/modules/access/groups-admin.service.js.map +1 -1
  67. package/dist/modules/access/reference-scan.d.ts +1 -1
  68. package/dist/modules/access/roles-admin.service.d.ts +7 -7
  69. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  70. package/dist/modules/access/roles-admin.service.js +8 -8
  71. package/dist/modules/access/roles-admin.service.js.map +1 -1
  72. package/dist/modules/access/synced-groups-committer.d.ts +4 -4
  73. package/dist/modules/access/synced-groups-committer.d.ts.map +1 -1
  74. package/dist/modules/access/synced-groups-committer.js +7 -7
  75. package/dist/modules/access/synced-groups-committer.js.map +1 -1
  76. package/dist/modules/access/user-access-removal.service.d.ts +6 -6
  77. package/dist/modules/access/user-access-removal.service.d.ts.map +1 -1
  78. package/dist/modules/access/user-access-removal.service.js +4 -4
  79. package/dist/modules/access/user-access-removal.service.js.map +1 -1
  80. package/dist/modules/access-model/access-errors.d.ts +4 -4
  81. package/dist/modules/access-model/access-errors.d.ts.map +1 -1
  82. package/dist/modules/access-model/access-errors.js +4 -4
  83. package/dist/modules/access-model/access-errors.js.map +1 -1
  84. package/dist/modules/access-model/access-grammar.d.ts +1 -1
  85. package/dist/modules/access-model/access-grammar.js +4 -4
  86. package/dist/modules/access-model/change-gate.d.ts +1 -1
  87. package/dist/modules/access-model/change-gate.js +1 -1
  88. package/dist/modules/access-model/roles-yaml-guard.d.ts +4 -4
  89. package/dist/modules/access-model/roles-yaml-guard.d.ts.map +1 -1
  90. package/dist/modules/access-model/roles-yaml-guard.js +4 -4
  91. package/dist/modules/access-model/roles-yaml-guard.js.map +1 -1
  92. package/dist/modules/agent-guide/agent-guide.d.ts +19 -14
  93. package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
  94. package/dist/modules/agent-guide/agent-guide.js +42 -18
  95. package/dist/modules/agent-guide/agent-guide.js.map +1 -1
  96. package/dist/modules/agent-guide/guide-budget.d.ts +5 -2
  97. package/dist/modules/agent-guide/guide-budget.d.ts.map +1 -1
  98. package/dist/modules/agent-guide/guide-budget.js +6 -2
  99. package/dist/modules/agent-guide/guide-budget.js.map +1 -1
  100. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +12 -5
  101. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -1
  102. package/dist/modules/agent-instructions/agent-instructions.routes.js +13 -6
  103. package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -1
  104. package/dist/modules/agent-instructions/compose.d.ts +15 -8
  105. package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
  106. package/dist/modules/agent-instructions/compose.js +24 -10
  107. package/dist/modules/agent-instructions/compose.js.map +1 -1
  108. package/dist/modules/agent-instructions/shared-file-rules.d.ts +14 -6
  109. package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
  110. package/dist/modules/agent-instructions/shared-file-rules.js +51 -11
  111. package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
  112. package/dist/modules/auth/account-erasure.service.d.ts +36 -7
  113. package/dist/modules/auth/account-erasure.service.d.ts.map +1 -1
  114. package/dist/modules/auth/account-erasure.service.js +30 -10
  115. package/dist/modules/auth/account-erasure.service.js.map +1 -1
  116. package/dist/modules/auth/account-hooks.d.ts +20 -0
  117. package/dist/modules/auth/account-hooks.d.ts.map +1 -1
  118. package/dist/modules/auth/account-hooks.js +10 -0
  119. package/dist/modules/auth/account-hooks.js.map +1 -1
  120. package/dist/modules/auth/account-switch-lock.js +2 -2
  121. package/dist/modules/auth/account-switch-lock.js.map +1 -1
  122. package/dist/modules/auth/account.routes.d.ts +7 -2
  123. package/dist/modules/auth/account.routes.d.ts.map +1 -1
  124. package/dist/modules/auth/account.routes.js +23 -4
  125. package/dist/modules/auth/account.routes.js.map +1 -1
  126. package/dist/modules/auth/auth.service.d.ts +1 -1
  127. package/dist/modules/auth/auth.service.js +4 -4
  128. package/dist/modules/auth/auth.service.js.map +1 -1
  129. package/dist/modules/auth/index.d.ts +1 -0
  130. package/dist/modules/auth/index.d.ts.map +1 -1
  131. package/dist/modules/database/core-schema.js +2 -2
  132. package/dist/modules/database/core-schema.js.map +1 -1
  133. package/dist/modules/diff/diff.routes.d.ts +3 -3
  134. package/dist/modules/diff/diff.routes.d.ts.map +1 -1
  135. package/dist/modules/diff/diff.routes.js +7 -7
  136. package/dist/modules/diff/diff.routes.js.map +1 -1
  137. package/dist/modules/embed/embed.routes.js +1 -1
  138. package/dist/modules/embed/embed.routes.js.map +1 -1
  139. package/dist/modules/embed/embed.service.d.ts +4 -4
  140. package/dist/modules/embed/embed.service.d.ts.map +1 -1
  141. package/dist/modules/embed/embed.service.js +18 -18
  142. package/dist/modules/embed/embed.service.js.map +1 -1
  143. package/dist/modules/kb-fs/branch-name.js +8 -8
  144. package/dist/modules/kb-fs/branch-name.js.map +1 -1
  145. package/dist/modules/kb-fs/file-change-notifier.d.ts +2 -2
  146. package/dist/modules/kb-fs/locking-filesystem.d.ts +9 -9
  147. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
  148. package/dist/modules/kb-fs/locking-filesystem.js +27 -27
  149. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
  150. package/dist/modules/kb-fs/repo-path.js +5 -5
  151. package/dist/modules/kb-fs/repo-path.js.map +1 -1
  152. package/dist/modules/kb-fs/tmp-ref.d.ts +2 -2
  153. package/dist/modules/kb-fs/tmp-ref.d.ts.map +1 -1
  154. package/dist/modules/kb-fs/tmp-ref.js +2 -2
  155. package/dist/modules/kb-fs/tmp-ref.js.map +1 -1
  156. package/dist/modules/kb-sync/index.d.ts +1 -1
  157. package/dist/modules/kb-sync/index.d.ts.map +1 -1
  158. package/dist/modules/kb-sync/kb-sync.interface.d.ts +3 -3
  159. package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -1
  160. package/dist/modules/kb-sync/kb-sync.service.d.ts +3 -3
  161. package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
  162. package/dist/modules/kb-sync/kb-sync.service.js +10 -10
  163. package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
  164. package/dist/modules/mcp/mcp.service.d.ts +11 -4
  165. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  166. package/dist/modules/mcp/mcp.service.js +75 -19
  167. package/dist/modules/mcp/mcp.service.js.map +1 -1
  168. package/dist/modules/onboarding/onboarding.contract.d.ts +1 -1
  169. package/dist/modules/onboarding/onboarding.routes.js +2 -2
  170. package/dist/modules/onboarding/onboarding.routes.js.map +1 -1
  171. package/dist/modules/onboarding/starter-pack.service.d.ts +4 -4
  172. package/dist/modules/onboarding/starter-pack.service.d.ts.map +1 -1
  173. package/dist/modules/onboarding/starter-pack.service.js +4 -4
  174. package/dist/modules/onboarding/starter-pack.service.js.map +1 -1
  175. package/dist/modules/plugins/join-request-jobs.service.d.ts +2 -2
  176. package/dist/modules/plugins/join-request-jobs.service.d.ts.map +1 -1
  177. package/dist/modules/plugins/join-request-jobs.service.js +7 -7
  178. package/dist/modules/plugins/join-request-jobs.service.js.map +1 -1
  179. package/dist/modules/plugins/join-requests.service.d.ts +3 -3
  180. package/dist/modules/plugins/join-requests.service.d.ts.map +1 -1
  181. package/dist/modules/plugins/join-requests.service.js +5 -5
  182. package/dist/modules/plugins/join-requests.service.js.map +1 -1
  183. package/dist/modules/plugins/plugin-rename.service.js +1 -1
  184. package/dist/modules/plugins/plugin-rename.service.js.map +1 -1
  185. package/dist/modules/plugins/plugins.routes.d.ts +2 -2
  186. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  187. package/dist/modules/plugins/plugins.routes.js +8 -8
  188. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  189. package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +77 -2
  190. package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
  191. package/dist/modules/secrets-vault/db-secrets-vault.service.js +479 -53
  192. package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
  193. package/dist/modules/secrets-vault/index.d.ts +2 -1
  194. package/dist/modules/secrets-vault/index.d.ts.map +1 -1
  195. package/dist/modules/secrets-vault/index.js +1 -0
  196. package/dist/modules/secrets-vault/index.js.map +1 -1
  197. package/dist/modules/secrets-vault/managed-sign-in-erasure.d.ts +36 -0
  198. package/dist/modules/secrets-vault/managed-sign-in-erasure.d.ts.map +1 -0
  199. package/dist/modules/secrets-vault/managed-sign-in-erasure.js +37 -0
  200. package/dist/modules/secrets-vault/managed-sign-in-erasure.js.map +1 -0
  201. package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +152 -0
  202. package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
  203. package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
  204. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +7 -1
  205. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
  206. package/dist/modules/secrets-vault/secrets-vault.routes.js +125 -10
  207. package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
  208. package/dist/modules/settings/connection-check.js +1 -1
  209. package/dist/modules/settings/connection-check.js.map +1 -1
  210. package/dist/modules/settings/deployment-settings.service.js +1 -1
  211. package/dist/modules/settings/git-root-folders.js +1 -1
  212. package/dist/modules/settings/git-root-folders.js.map +1 -1
  213. package/dist/modules/settings/setup.routes.d.ts +1 -1
  214. package/dist/modules/settings/setup.routes.js +1 -1
  215. package/dist/modules/skills/pending-skills.service.d.ts +3 -3
  216. package/dist/modules/skills/pending-skills.service.d.ts.map +1 -1
  217. package/dist/modules/skills/pending-skills.service.js +4 -4
  218. package/dist/modules/skills/pending-skills.service.js.map +1 -1
  219. package/dist/modules/skills/skill-access-requests.routes.d.ts +2 -2
  220. package/dist/modules/skills/skill-access-requests.routes.d.ts.map +1 -1
  221. package/dist/modules/skills/skill-access-requests.routes.js +5 -5
  222. package/dist/modules/skills/skill-access-requests.routes.js.map +1 -1
  223. package/dist/modules/tool-helpers/tool-context.d.ts +4 -4
  224. package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
  225. package/dist/modules/tool-helpers/tool-context.js +2 -2
  226. package/dist/modules/tool-helpers/tool-context.js.map +1 -1
  227. package/dist/modules/tool-helpers/tool-handler.js +2 -2
  228. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  229. package/dist/modules/tool-helpers/tool.contract.d.ts +4 -4
  230. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  231. package/dist/modules/tool-manuals/mcp-server-edit.service.js +1 -1
  232. package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -1
  233. package/dist/modules/tool-manuals/pending-tools.service.d.ts +3 -3
  234. package/dist/modules/tool-manuals/pending-tools.service.d.ts.map +1 -1
  235. package/dist/modules/tool-manuals/pending-tools.service.js +4 -4
  236. package/dist/modules/tool-manuals/pending-tools.service.js.map +1 -1
  237. package/dist/modules/tool-manuals/tool-delete.service.js +1 -1
  238. package/dist/modules/tool-manuals/tool-delete.service.js.map +1 -1
  239. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +43 -3
  240. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  241. package/dist/modules/tool-manuals/tool-manuals.service.d.ts +6 -0
  242. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  243. package/dist/modules/tool-manuals/tool-manuals.service.js +51 -5
  244. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  245. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +3 -0
  246. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  247. package/dist/modules/tool-manuals/tool-manuals.tools.js +36 -15
  248. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  249. package/dist/modules/version-control/agent-tools/change-request-read-shape.d.ts.map +1 -0
  250. package/dist/modules/version-control/agent-tools/change-request-read-shape.js.map +1 -0
  251. package/dist/modules/version-control/agent-tools/change-request-read.tools.d.ts.map +1 -0
  252. package/dist/modules/{workflow → version-control}/agent-tools/change-request-read.tools.js +2 -2
  253. package/dist/modules/version-control/agent-tools/change-request-read.tools.js.map +1 -0
  254. package/dist/modules/version-control/agent-tools/change-request-summary.d.ts.map +1 -0
  255. package/dist/modules/version-control/agent-tools/change-request-summary.js.map +1 -0
  256. package/dist/modules/{workflow/agent-tools/workflow.tools.d.ts → version-control/agent-tools/version-control.tools.d.ts} +4 -4
  257. package/dist/modules/version-control/agent-tools/version-control.tools.d.ts.map +1 -0
  258. package/dist/modules/{workflow/agent-tools/workflow.tools.js → version-control/agent-tools/version-control.tools.js} +31 -31
  259. package/dist/modules/version-control/agent-tools/version-control.tools.js.map +1 -0
  260. package/dist/modules/{workflow → version-control}/change-request-hooks.d.ts +1 -1
  261. package/dist/modules/version-control/change-request-hooks.d.ts.map +1 -0
  262. package/dist/modules/version-control/change-request-hooks.js.map +1 -0
  263. package/dist/modules/version-control/change-request-review/approval-lock.d.ts.map +1 -0
  264. package/dist/modules/version-control/change-request-review/approval-lock.js.map +1 -0
  265. package/dist/modules/{workflow/review-workflow/review-workflow.interface.d.ts → version-control/change-request-review/change-request-review.interface.d.ts} +2 -2
  266. package/dist/modules/version-control/change-request-review/change-request-review.interface.d.ts.map +1 -0
  267. package/dist/modules/version-control/change-request-review/change-request-review.interface.js +2 -0
  268. package/dist/modules/version-control/change-request-review/change-request-review.interface.js.map +1 -0
  269. package/dist/modules/{workflow/review-workflow/review-workflow.service.d.ts → version-control/change-request-review/change-request-review.service.d.ts} +3 -3
  270. package/dist/modules/version-control/change-request-review/change-request-review.service.d.ts.map +1 -0
  271. package/dist/modules/{workflow/review-workflow/review-workflow.service.js → version-control/change-request-review/change-request-review.service.js} +26 -26
  272. package/dist/modules/version-control/change-request-review/change-request-review.service.js.map +1 -0
  273. package/dist/modules/{workflow → version-control}/event-bus.d.ts +8 -8
  274. package/dist/modules/version-control/event-bus.d.ts.map +1 -0
  275. package/dist/modules/{workflow → version-control}/event-bus.js +4 -4
  276. package/dist/modules/version-control/event-bus.js.map +1 -0
  277. package/dist/modules/{workflow → version-control}/events-delivery.d.ts +4 -4
  278. package/dist/modules/version-control/events-delivery.d.ts.map +1 -0
  279. package/dist/modules/version-control/events-delivery.js.map +1 -0
  280. package/dist/modules/{workflow → version-control}/events.routes.d.ts +4 -4
  281. package/dist/modules/version-control/events.routes.d.ts.map +1 -0
  282. package/dist/modules/version-control/events.routes.js.map +1 -0
  283. package/dist/modules/{workflow → version-control}/file-lock.service.d.ts +2 -2
  284. package/dist/modules/version-control/file-lock.service.d.ts.map +1 -0
  285. package/dist/modules/{workflow → version-control}/file-lock.service.js +6 -6
  286. package/dist/modules/version-control/file-lock.service.js.map +1 -0
  287. package/dist/modules/version-control/git/change-request-link.d.ts.map +1 -0
  288. package/dist/modules/version-control/git/change-request-link.js.map +1 -0
  289. package/dist/modules/version-control/git/git-version.d.ts.map +1 -0
  290. package/dist/modules/version-control/git/git-version.js.map +1 -0
  291. package/dist/modules/{workflow → version-control}/git/git.service.d.ts +8 -8
  292. package/dist/modules/version-control/git/git.service.d.ts.map +1 -0
  293. package/dist/modules/{workflow → version-control}/git/git.service.js +53 -53
  294. package/dist/modules/version-control/git/git.service.js.map +1 -0
  295. package/dist/modules/version-control/git/merge-commit.d.ts.map +1 -0
  296. package/dist/modules/version-control/git/merge-commit.js.map +1 -0
  297. package/dist/modules/version-control/git/node-git-runner.d.ts.map +1 -0
  298. package/dist/modules/{workflow → version-control}/git/node-git-runner.js +1 -1
  299. package/dist/modules/version-control/git/node-git-runner.js.map +1 -0
  300. package/dist/modules/{workflow → version-control}/git/pull-request.service.d.ts +5 -5
  301. package/dist/modules/version-control/git/pull-request.service.d.ts.map +1 -0
  302. package/dist/modules/{workflow → version-control}/git/pull-request.service.js +10 -10
  303. package/dist/modules/version-control/git/pull-request.service.js.map +1 -0
  304. package/dist/modules/{workflow → version-control}/index.d.ts +6 -5
  305. package/dist/modules/version-control/index.d.ts.map +1 -0
  306. package/dist/modules/{workflow → version-control}/index.js +3 -2
  307. package/dist/modules/version-control/index.js.map +1 -0
  308. package/dist/modules/{workflow → version-control}/pending-commits.service.d.ts +2 -2
  309. package/dist/modules/version-control/pending-commits.service.d.ts.map +1 -0
  310. package/dist/modules/{workflow → version-control}/pending-commits.service.js +2 -2
  311. package/dist/modules/version-control/pending-commits.service.js.map +1 -0
  312. package/dist/modules/{workflow → version-control}/pending-commits.worker.d.ts +5 -5
  313. package/dist/modules/version-control/pending-commits.worker.d.ts.map +1 -0
  314. package/dist/modules/{workflow → version-control}/pending-commits.worker.js +3 -3
  315. package/dist/modules/version-control/pending-commits.worker.js.map +1 -0
  316. package/dist/modules/{workflow → version-control}/read-on-branch.d.ts +2 -2
  317. package/dist/modules/version-control/read-on-branch.d.ts.map +1 -0
  318. package/dist/modules/{workflow → version-control}/read-on-branch.js +2 -2
  319. package/dist/modules/version-control/read-on-branch.js.map +1 -0
  320. package/dist/modules/version-control/recovery-bot.d.ts.map +1 -0
  321. package/dist/modules/version-control/recovery-bot.js.map +1 -0
  322. package/dist/modules/version-control/sanitize-error.d.ts.map +1 -0
  323. package/dist/modules/version-control/sanitize-error.js.map +1 -0
  324. package/dist/modules/{workflow/workflow-hooks.d.ts → version-control/version-control-hooks.d.ts} +4 -4
  325. package/dist/modules/version-control/version-control-hooks.d.ts.map +1 -0
  326. package/dist/modules/{workflow/workflow-hooks.js → version-control/version-control-hooks.js} +3 -3
  327. package/dist/modules/version-control/version-control-hooks.js.map +1 -0
  328. package/dist/modules/{workflow/workflow.routes.d.ts → version-control/version-control.routes.d.ts} +6 -5
  329. package/dist/modules/version-control/version-control.routes.d.ts.map +1 -0
  330. package/dist/modules/{workflow/workflow.routes.js → version-control/version-control.routes.js} +61 -60
  331. package/dist/modules/version-control/version-control.routes.js.map +1 -0
  332. package/dist/modules/{workflow/workflow.service.d.ts → version-control/version-control.service.d.ts} +26 -31
  333. package/dist/modules/version-control/version-control.service.d.ts.map +1 -0
  334. package/dist/modules/{workflow/workflow.service.js → version-control/version-control.service.js} +89 -94
  335. package/dist/modules/version-control/version-control.service.js.map +1 -0
  336. package/dist/modules/workspace/agent-access.gate.d.ts +5 -5
  337. package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -1
  338. package/dist/modules/workspace/agent-access.gate.js +1 -1
  339. package/dist/modules/workspace/git-internals.middleware.d.ts +1 -1
  340. package/dist/modules/workspace/git-internals.middleware.js +2 -2
  341. package/dist/modules/workspace/kb-repository.js +1 -1
  342. package/dist/modules/workspace/kb-repository.js.map +1 -1
  343. package/dist/modules/workspace/startup/kb-git.d.ts +1 -1
  344. package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -1
  345. package/dist/modules/workspace/startup/kb-git.js +1 -1
  346. package/dist/modules/workspace/startup/kb-git.js.map +1 -1
  347. package/dist/modules/workspace/startup/steps/seed-tree.d.ts +2 -2
  348. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
  349. package/dist/modules/workspace/startup/steps/seed-tree.js +1 -1
  350. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
  351. package/dist/modules/workspace/startup/steps/template-files.step.d.ts +2 -2
  352. package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
  353. package/dist/modules/workspace/startup/steps/template-files.step.js +2 -2
  354. package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
  355. package/dist/modules/workspace/startup/steps/template-source.d.ts +17 -4
  356. package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -1
  357. package/dist/modules/workspace/startup/steps/template-source.js +8 -5
  358. package/dist/modules/workspace/startup/steps/template-source.js.map +1 -1
  359. package/dist/modules/workspace/workspace-tmp.js +3 -3
  360. package/dist/modules/workspace/workspace-tmp.js.map +1 -1
  361. package/dist/modules/workspace/workspace.routes.d.ts +4 -4
  362. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  363. package/dist/modules/workspace/workspace.routes.js +22 -22
  364. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  365. package/dist/modules/workspace/workspace.service.d.ts +6 -6
  366. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  367. package/dist/modules/workspace/workspace.service.js +11 -11
  368. package/dist/modules/workspace/workspace.service.js.map +1 -1
  369. package/dist/modules/workspace/workspace.tools.d.ts +2 -1
  370. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  371. package/dist/modules/workspace/workspace.tools.js +15 -10
  372. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  373. package/dist/shared/canonical-file-identity.d.ts +2 -2
  374. package/dist/shared/canonical-file-identity.js +5 -5
  375. package/dist/shared/canonical-file-identity.js.map +1 -1
  376. package/dist/shared/domain-errors.d.ts +39 -39
  377. package/dist/shared/domain-errors.d.ts.map +1 -1
  378. package/dist/shared/domain-errors.js +44 -44
  379. package/dist/shared/domain-errors.js.map +1 -1
  380. package/dist/shared/git.contract.d.ts +1 -1
  381. package/dist/shared/git.contract.js +1 -1
  382. package/dist/shared/http-errors.d.ts +3 -3
  383. package/dist/shared/http-errors.d.ts.map +1 -1
  384. package/dist/shared/http-errors.js +1 -1
  385. package/dist/shared/http-errors.js.map +1 -1
  386. package/dist/shared/pending-proposals.d.ts +3 -3
  387. package/dist/shared/pending-proposals.d.ts.map +1 -1
  388. package/dist/shared/pending-proposals.js +2 -2
  389. package/dist/shared/pending-proposals.js.map +1 -1
  390. package/dist/tenancy/static-tenant-source.js +1 -1
  391. package/dist/tenancy/static-tenant-source.js.map +1 -1
  392. package/migrations/0012_agent_audit.sql +40 -40
  393. package/migrations/0013_agent_identity_and_key_soft_delete.sql +29 -29
  394. package/migrations/meta/0012_snapshot.json +2246 -2246
  395. package/migrations/meta/0013_snapshot.json +2258 -2258
  396. package/package.json +4 -4
  397. package/src/__tests__/account-admin.ts +27 -0
  398. package/src/__tests__/hexis-catalog.ts +4 -4
  399. package/src/__tests__/kb-context.ts +30 -30
  400. package/src/__tests__/kb-layout-config.test.ts +21 -0
  401. package/src/__tests__/open-change-gate.ts +1 -1
  402. package/src/__tests__/tool-handler.ts +15 -0
  403. package/src/__tests__/tool-wiring.ts +10 -4
  404. package/src/__tests__/workspace-service.ts +1 -1
  405. package/src/core/__tests__/catalog-cache-invalidation.test.ts +3 -3
  406. package/src/core/__tests__/compose-embed.test.ts +1 -1
  407. package/src/core/__tests__/damaged-document.tools.test.ts +3 -3
  408. package/src/core/__tests__/erasure-slices-wired-once.test.ts +46 -0
  409. package/src/core/__tests__/gated-boot.test.ts +2 -2
  410. package/src/core/__tests__/lifecycle.test.ts +9 -9
  411. package/src/core/catalog-cache-invalidation.ts +2 -2
  412. package/src/core/compose-embed.ts +3 -3
  413. package/src/core/core-ports.ts +19 -4
  414. package/src/core/create-core-server.ts +39 -25
  415. package/src/core/create-core-services.ts +79 -63
  416. package/src/core/lifecycle.ts +9 -9
  417. package/src/core-config.ts +3 -3
  418. package/src/index.ts +5 -5
  419. package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +4 -4
  420. package/src/modules/access/__tests__/access-own-read-grant.test.ts +14 -14
  421. package/src/modules/access/__tests__/access-requests.routes.test.ts +20 -20
  422. package/src/modules/access/__tests__/access.routes.admins.test.ts +4 -4
  423. package/src/modules/access/__tests__/access.routes.batch.test.ts +4 -4
  424. package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +6 -6
  425. package/src/modules/access/__tests__/access.routes.group-grant.test.ts +6 -6
  426. package/src/modules/access/__tests__/access.routes.overrides.test.ts +5 -5
  427. package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +6 -6
  428. package/src/modules/access/__tests__/access.routes.prospective.test.ts +3 -3
  429. package/src/modules/access/__tests__/access.routes.revoke.test.ts +6 -6
  430. package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +6 -6
  431. package/src/modules/access/__tests__/admin-locked-commit.test.ts +6 -6
  432. package/src/modules/access/__tests__/admin-route-helpers.test.ts +5 -5
  433. package/src/modules/access/__tests__/groups-admin.service.test.ts +27 -27
  434. package/src/modules/access/__tests__/roles-admin.security.test.ts +14 -13
  435. package/src/modules/access/__tests__/roles-admin.service.test.ts +27 -27
  436. package/src/modules/access/__tests__/roles-capabilities.test.ts +19 -19
  437. package/src/modules/access/__tests__/roles.routes.test.ts +6 -6
  438. package/src/modules/access/__tests__/synced-groups-committer.test.ts +13 -13
  439. package/src/modules/access/__tests__/user-access-removal.service.test.ts +29 -29
  440. package/src/modules/access/access-control.service.ts +1 -1
  441. package/src/modules/access/access-declarations.ts +2 -2
  442. package/src/modules/access/access-mutation.service.ts +2 -2
  443. package/src/modules/access/access-requests.routes.ts +7 -7
  444. package/src/modules/access/access-requests.service.ts +13 -13
  445. package/src/modules/access/access.routes.ts +11 -11
  446. package/src/modules/access/admin-locked-commit.ts +11 -11
  447. package/src/modules/access/admin-route-helpers.ts +5 -5
  448. package/src/modules/access/change-read-gate.ts +1 -1
  449. package/src/modules/access/groups-admin.service.ts +7 -7
  450. package/src/modules/access/reference-scan.ts +1 -1
  451. package/src/modules/access/roles-admin.service.ts +9 -9
  452. package/src/modules/access/synced-groups-committer.ts +11 -11
  453. package/src/modules/access/user-access-removal.service.ts +7 -7
  454. package/src/modules/access-model/__tests__/access-grammar.test.ts +2 -2
  455. package/src/modules/access-model/access-errors.ts +4 -4
  456. package/src/modules/access-model/access-grammar.ts +4 -4
  457. package/src/modules/access-model/change-gate.ts +1 -1
  458. package/src/modules/access-model/roles-yaml-guard.ts +4 -4
  459. package/src/modules/agent-guide/__tests__/agent-guide.test.ts +19 -4
  460. package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +1 -1
  461. package/src/modules/agent-guide/__tests__/guide-matches-code.test.ts +2 -2
  462. package/src/modules/agent-guide/agent-guide.ts +48 -22
  463. package/src/modules/agent-guide/guide-budget.ts +6 -2
  464. package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +4 -5
  465. package/src/modules/agent-instructions/__tests__/compose.test.ts +1 -1
  466. package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +113 -7
  467. package/src/modules/agent-instructions/agent-instructions.routes.ts +12 -5
  468. package/src/modules/agent-instructions/compose.ts +24 -9
  469. package/src/modules/agent-instructions/shared-file-rules.ts +53 -11
  470. package/src/modules/agent-scope/__tests__/agent-surfaces.connect-as.test.ts +1 -1
  471. package/src/modules/audit/__tests__/agent-rest-audit.middleware.test.ts +113 -113
  472. package/src/modules/audit/__tests__/event-classifier.test.ts +121 -121
  473. package/src/modules/audit/event-classifier.ts +155 -155
  474. package/src/modules/auth/__tests__/account-deactivation.test.ts +1 -1
  475. package/src/modules/auth/__tests__/account-erasure.approval-gate.test.ts +4 -4
  476. package/src/modules/auth/__tests__/account-erasure.participants.test.ts +56 -0
  477. package/src/modules/auth/__tests__/account-switch-lock.pg.test.ts +2 -2
  478. package/src/modules/auth/__tests__/account-switch-lock.test.ts +2 -2
  479. package/src/modules/auth/__tests__/account.routes.test.ts +73 -11
  480. package/src/modules/auth/__tests__/auth.service.test.ts +1 -1
  481. package/src/modules/auth/account-erasure.service.ts +27 -9
  482. package/src/modules/auth/account-hooks.ts +34 -0
  483. package/src/modules/auth/account-switch-lock.ts +2 -2
  484. package/src/modules/auth/account.routes.ts +23 -5
  485. package/src/modules/auth/auth.service.ts +4 -4
  486. package/src/modules/auth/index.ts +1 -0
  487. package/src/modules/database/__tests__/advisory-lock.test.ts +169 -169
  488. package/src/modules/database/__tests__/migrations-unqualified.test.ts +32 -32
  489. package/src/modules/database/core-schema.ts +2 -2
  490. package/src/modules/diff/__tests__/diff.routes.read-gate.test.ts +2 -2
  491. package/src/modules/diff/__tests__/diff.routes.rejectPathsLocked.test.ts +12 -12
  492. package/src/modules/diff/diff.routes.ts +8 -8
  493. package/src/modules/embed/__tests__/embed.service.lock.test.ts +7 -7
  494. package/src/modules/embed/__tests__/embed.service.test.ts +42 -42
  495. package/src/modules/embed/__tests__/embed.tools.test.ts +1 -1
  496. package/src/modules/embed/embed.routes.ts +1 -1
  497. package/src/modules/embed/embed.service.ts +17 -17
  498. package/src/modules/kb-fs/__tests__/branch-name.test.ts +2 -2
  499. package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +260 -260
  500. package/src/modules/kb-fs/__tests__/move-with-edits.test.ts +34 -34
  501. package/src/modules/kb-fs/__tests__/repo-path.test.ts +8 -8
  502. package/src/modules/kb-fs/branch-name.ts +8 -8
  503. package/src/modules/kb-fs/file-change-notifier.ts +2 -2
  504. package/src/modules/kb-fs/locking-filesystem.ts +30 -30
  505. package/src/modules/kb-fs/repo-path.ts +5 -5
  506. package/src/modules/kb-fs/tmp-ref.ts +3 -3
  507. package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +36 -36
  508. package/src/modules/kb-sync/index.ts +1 -1
  509. package/src/modules/kb-sync/kb-sync.interface.ts +3 -3
  510. package/src/modules/kb-sync/kb-sync.service.ts +9 -9
  511. package/src/modules/marketplace/__tests__/github-facade.test.ts +1 -1
  512. package/src/modules/marketplace/__tests__/marketplace-git.test.ts +1 -1
  513. package/src/modules/mcp/__tests__/mcp-proxy-helpers.test.ts +4 -2
  514. package/src/modules/mcp/__tests__/mcp.e2e.test.ts +69 -8
  515. package/src/modules/mcp/__tests__/mcp.service.test.ts +91 -13
  516. package/src/modules/mcp/__tests__/read-only-hint.pg.test.ts +194 -0
  517. package/src/modules/mcp/mcp.service.ts +89 -28
  518. package/src/modules/onboarding/__tests__/starter-pack.service.test.ts +55 -55
  519. package/src/modules/onboarding/onboarding.contract.ts +1 -1
  520. package/src/modules/onboarding/onboarding.routes.ts +2 -2
  521. package/src/modules/onboarding/starter-pack.service.ts +6 -6
  522. package/src/modules/plugins/__tests__/bundle-dialect.e2e.test.ts +1 -1
  523. package/src/modules/plugins/__tests__/compile-marketplace.test.ts +1 -1
  524. package/src/modules/plugins/__tests__/join-request-jobs.service.test.ts +43 -43
  525. package/src/modules/plugins/__tests__/join-requests.service.test.ts +19 -19
  526. package/src/modules/plugins/__tests__/join-requests.settlement.test.ts +26 -26
  527. package/src/modules/plugins/__tests__/plugins.routes.test.ts +25 -25
  528. package/src/modules/plugins/join-request-jobs.service.ts +10 -10
  529. package/src/modules/plugins/join-requests.service.ts +4 -4
  530. package/src/modules/plugins/plugin-rename.service.ts +1 -1
  531. package/src/modules/plugins/plugins.routes.ts +9 -9
  532. package/src/modules/secrets-vault/__tests__/db-secrets-vault.managed.test.ts +773 -0
  533. package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +28 -1
  534. package/src/modules/secrets-vault/__tests__/managed-sign-in.route.test.ts +351 -0
  535. package/src/modules/secrets-vault/db-secrets-vault.service.ts +562 -95
  536. package/src/modules/secrets-vault/index.ts +7 -0
  537. package/src/modules/secrets-vault/managed-sign-in-erasure.ts +41 -0
  538. package/src/modules/secrets-vault/secrets-variable-loader.ts +134 -134
  539. package/src/modules/secrets-vault/secrets-vault.contract.ts +152 -0
  540. package/src/modules/secrets-vault/secrets-vault.routes.ts +157 -10
  541. package/src/modules/settings/__tests__/repository-source.test.ts +1 -1
  542. package/src/modules/settings/__tests__/setup.routes.layout-phase.test.ts +1 -1
  543. package/src/modules/settings/__tests__/setup.routes.managed-phase.test.ts +1 -1
  544. package/src/modules/settings/connection-check.ts +1 -1
  545. package/src/modules/settings/deployment-settings.service.ts +1 -1
  546. package/src/modules/settings/git-root-folders.ts +1 -1
  547. package/src/modules/settings/setup.routes.ts +1 -1
  548. package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +3 -3
  549. package/src/modules/skills/__tests__/branch-skills.tools.test.ts +1 -1
  550. package/src/modules/skills/__tests__/pending-skills.service.test.ts +8 -8
  551. package/src/modules/skills/__tests__/skill-access-requests.routes.test.ts +18 -18
  552. package/src/modules/skills/__tests__/skills.service.test.ts +3 -3
  553. package/src/modules/skills/pending-skills.service.ts +3 -3
  554. package/src/modules/skills/skill-access-requests.routes.ts +7 -7
  555. package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +4 -4
  556. package/src/modules/tool-helpers/__tests__/branch-declaration.test.ts +3 -3
  557. package/src/modules/tool-helpers/__tests__/can-write.tools.test.ts +3 -3
  558. package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +4 -4
  559. package/src/modules/tool-helpers/__tests__/tools-surface.test.ts +3 -3
  560. package/src/modules/tool-helpers/__tests__/validate-token.test.ts +1 -1
  561. package/src/modules/tool-helpers/tool-context.ts +6 -6
  562. package/src/modules/tool-helpers/tool-handler.ts +2 -2
  563. package/src/modules/tool-helpers/tool.contract.ts +4 -4
  564. package/src/modules/tool-manuals/__tests__/pending-tools.service.test.ts +9 -9
  565. package/src/modules/tool-manuals/__tests__/tool-manuals.managed-sign-in.test.ts +89 -0
  566. package/src/modules/tool-manuals/__tests__/tool-manuals.restart.test.ts +1 -1
  567. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +36 -1
  568. package/src/modules/tool-manuals/mcp-server-edit.service.ts +1 -1
  569. package/src/modules/tool-manuals/pending-tools.service.ts +3 -3
  570. package/src/modules/tool-manuals/tool-delete.service.ts +1 -1
  571. package/src/modules/tool-manuals/tool-manuals.contract.ts +41 -3
  572. package/src/modules/tool-manuals/tool-manuals.service.ts +53 -5
  573. package/src/modules/tool-manuals/tool-manuals.tools.ts +41 -15
  574. package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +4 -4
  575. package/src/modules/tool-registry/__tests__/read-only-hints.test.ts +3 -3
  576. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +6 -6
  577. package/src/modules/{workflow → version-control}/__tests__/apply-failure.test.ts +54 -54
  578. package/src/modules/{workflow → version-control}/__tests__/event-bus.test.ts +27 -27
  579. package/src/modules/{workflow → version-control}/__tests__/events-delivery.test.ts +37 -37
  580. package/src/modules/{workflow → version-control}/__tests__/file-lock.service.deletion-gate.test.ts +2 -2
  581. package/src/modules/{workflow → version-control}/__tests__/file-lock.service.path-identity.test.ts +8 -8
  582. package/src/modules/{workflow → version-control}/__tests__/merge-announces-tree.test.ts +11 -11
  583. package/src/modules/{workflow → version-control}/__tests__/pending-commits.worker.concurrency.test.ts +9 -9
  584. package/src/modules/{workflow → version-control}/__tests__/pending-commits.worker.test.ts +25 -25
  585. package/src/modules/{workflow → version-control}/__tests__/preserve-roles-yaml.test.ts +42 -42
  586. package/src/modules/version-control/__tests__/public-surface.test.ts +114 -0
  587. package/src/modules/{workflow/__tests__/workflow.routes.folder-proposals.test.ts → version-control/__tests__/version-control.routes.folder-proposals.test.ts} +18 -18
  588. package/src/modules/{workflow/__tests__/workflow.routes.fork-point-file.test.ts → version-control/__tests__/version-control.routes.fork-point-file.test.ts} +19 -19
  589. package/src/modules/{workflow/__tests__/workflow.routes.history-read-gate.test.ts → version-control/__tests__/version-control.routes.history-read-gate.test.ts} +13 -13
  590. package/src/modules/{workflow/__tests__/workflow.routes.lock-path.test.ts → version-control/__tests__/version-control.routes.lock-path.test.ts} +18 -18
  591. package/src/modules/{workflow/__tests__/workflow.routes.open-change-request.test.ts → version-control/__tests__/version-control.routes.open-change-request.test.ts} +7 -7
  592. package/src/modules/{workflow/__tests__/workflow.routes.push-refused.test.ts → version-control/__tests__/version-control.routes.push-refused.test.ts} +14 -14
  593. package/src/modules/{workflow/__tests__/workflow.service.branch-in-use.test.ts → version-control/__tests__/version-control.service.branch-in-use.test.ts} +6 -6
  594. package/src/modules/{workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts → version-control/__tests__/version-control.service.commitFileWhileLocked.test.ts} +14 -14
  595. package/src/modules/{workflow/__tests__/workflow.service.delete-branch.test.ts → version-control/__tests__/version-control.service.delete-branch.test.ts} +14 -14
  596. package/src/modules/{workflow/__tests__/workflow.service.deleted-branch-sweep.test.ts → version-control/__tests__/version-control.service.deleted-branch-sweep.test.ts} +4 -4
  597. package/src/modules/{workflow/__tests__/workflow.service.facade.test.ts → version-control/__tests__/version-control.service.facade.test.ts} +97 -99
  598. package/src/modules/{workflow/__tests__/workflow.service.folder-proposals.test.ts → version-control/__tests__/version-control.service.folder-proposals.test.ts} +7 -7
  599. package/src/modules/{workflow/__tests__/workflow.service.platform-restore.test.ts → version-control/__tests__/version-control.service.platform-restore.test.ts} +6 -6
  600. package/src/modules/{workflow/__tests__/workflow.service.read-gate.test.ts → version-control/__tests__/version-control.service.read-gate.test.ts} +9 -9
  601. package/src/modules/{workflow/__tests__/workflow.service.releaseLock.test.ts → version-control/__tests__/version-control.service.releaseLock.test.ts} +42 -42
  602. package/src/modules/{workflow/__tests__/workflow.service.repository-replaced.test.ts → version-control/__tests__/version-control.service.repository-replaced.test.ts} +4 -4
  603. package/src/modules/{workflow/__tests__/workflow.service.sync.test.ts → version-control/__tests__/version-control.service.sync.test.ts} +17 -17
  604. package/src/modules/{workflow/__tests__/workflow.service.update-change-request.pg.test.ts → version-control/__tests__/version-control.service.update-change-request.pg.test.ts} +6 -6
  605. package/src/modules/{workflow/__tests__/workflow.service.update-change-request.test.ts → version-control/__tests__/version-control.service.update-change-request.test.ts} +11 -11
  606. package/src/modules/{workflow/__tests__/workflow.service.update-from-target.test.ts → version-control/__tests__/version-control.service.update-from-target.test.ts} +22 -22
  607. package/src/modules/{workflow → version-control}/agent-tools/__tests__/change-request-read.tools.test.ts +3 -3
  608. package/src/modules/{workflow → version-control}/agent-tools/__tests__/update-change-request.parity.test.ts +16 -16
  609. package/src/modules/{workflow/agent-tools/__tests__/workflow.tools.test.ts → version-control/agent-tools/__tests__/version-control.tools.test.ts} +9 -9
  610. package/src/modules/{workflow → version-control}/agent-tools/change-request-read.tools.ts +2 -2
  611. package/src/modules/{workflow/agent-tools/workflow.tools.ts → version-control/agent-tools/version-control.tools.ts} +30 -30
  612. package/src/modules/{workflow → version-control}/change-request-hooks.ts +1 -1
  613. package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/approval-states.test.ts +5 -5
  614. package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/cancel-pr.test.ts +8 -8
  615. package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/carry-approvals-forward.test.ts +6 -6
  616. package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/erase-approver.test.ts +5 -5
  617. package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/merge-gate.test.ts +3 -3
  618. package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/merge-records-own-commit.test.ts +3 -3
  619. package/src/modules/{workflow/review-workflow/review-workflow.interface.ts → version-control/change-request-review/change-request-review.interface.ts} +1 -1
  620. package/src/modules/{workflow/review-workflow/review-workflow.service.ts → version-control/change-request-review/change-request-review.service.ts} +28 -28
  621. package/src/modules/{workflow → version-control}/event-bus.ts +14 -14
  622. package/src/modules/{workflow → version-control}/events-delivery.ts +11 -11
  623. package/src/modules/{workflow → version-control}/events.routes.ts +5 -5
  624. package/src/modules/{workflow → version-control}/file-lock.service.ts +6 -6
  625. package/src/modules/{workflow → version-control}/git/__tests__/git-test-helpers.ts +3 -3
  626. package/src/modules/{workflow → version-control}/git/__tests__/git.service.accessGating.test.ts +5 -5
  627. package/src/modules/{workflow → version-control}/git/__tests__/git.service.appliedChange.test.ts +4 -4
  628. package/src/modules/{workflow → version-control}/git/__tests__/git.service.changedFilesForPr.test.ts +16 -16
  629. package/src/modules/{workflow → version-control}/git/__tests__/git.service.changedPathsMemo.test.ts +2 -2
  630. package/src/modules/{workflow → version-control}/git/__tests__/git.service.commitChanges.test.ts +3 -3
  631. package/src/modules/{workflow → version-control}/git/__tests__/git.service.commitFile.strayPath.test.ts +10 -10
  632. package/src/modules/{workflow → version-control}/git/__tests__/git.service.commitFile.test.ts +3 -3
  633. package/src/modules/{workflow → version-control}/git/__tests__/git.service.createBranch.test.ts +3 -3
  634. package/src/modules/{workflow → version-control}/git/__tests__/git.service.deleteBranch.test.ts +7 -7
  635. package/src/modules/{workflow → version-control}/git/__tests__/git.service.diffFileAtCommit.test.ts +9 -9
  636. package/src/modules/{workflow → version-control}/git/__tests__/git.service.diffFileBetweenBranches.test.ts +23 -23
  637. package/src/modules/{workflow → version-control}/git/__tests__/git.service.fileBytesAtCommit.test.ts +7 -7
  638. package/src/modules/{workflow → version-control}/git/__tests__/git.service.fileContentsAtCommit.test.ts +3 -3
  639. package/src/modules/{workflow → version-control}/git/__tests__/git.service.forkPoint.test.ts +2 -2
  640. package/src/modules/{workflow → version-control}/git/__tests__/git.service.history-guards.test.ts +2 -2
  641. package/src/modules/{workflow → version-control}/git/__tests__/git.service.list-branches.test.ts +12 -12
  642. package/src/modules/{workflow → version-control}/git/__tests__/git.service.listTrackedFiles.test.ts +2 -2
  643. package/src/modules/{workflow → version-control}/git/__tests__/git.service.logForFile.test.ts +6 -6
  644. package/src/modules/{workflow → version-control}/git/__tests__/git.service.mergeChangeRequest.test.ts +20 -20
  645. package/src/modules/{workflow → version-control}/git/__tests__/git.service.mergeIntoBranch.test.ts +16 -16
  646. package/src/modules/{workflow → version-control}/git/__tests__/git.service.pendingChanges.test.ts +2 -2
  647. package/src/modules/{workflow → version-control}/git/__tests__/git.service.postMergePull.test.ts +5 -5
  648. package/src/modules/{workflow → version-control}/git/__tests__/git.service.prFetchFailure.test.ts +2 -2
  649. package/src/modules/{workflow → version-control}/git/__tests__/git.service.pull.test.ts +12 -12
  650. package/src/modules/{workflow → version-control}/git/__tests__/git.service.readFileAtRef.test.ts +3 -3
  651. package/src/modules/{workflow → version-control}/git/__tests__/git.service.revertPathsAndPush.test.ts +2 -2
  652. package/src/modules/{workflow → version-control}/git/__tests__/git.service.status.test.ts +4 -4
  653. package/src/modules/{workflow → version-control}/git/__tests__/git.service.syncFromRemote.test.ts +8 -8
  654. package/src/modules/{workflow → version-control}/git/__tests__/git.service.worktrees.test.ts +4 -4
  655. package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.list-fetch.test.ts +2 -2
  656. package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.test.ts +6 -6
  657. package/src/modules/{workflow → version-control}/git/git.service.ts +57 -57
  658. package/src/modules/{workflow → version-control}/git/node-git-runner.ts +1 -1
  659. package/src/modules/{workflow → version-control}/git/pull-request.service.ts +12 -12
  660. package/src/modules/{workflow → version-control}/index.ts +6 -5
  661. package/src/modules/{workflow → version-control}/pending-commits.service.ts +2 -2
  662. package/src/modules/{workflow → version-control}/pending-commits.worker.ts +8 -8
  663. package/src/modules/{workflow → version-control}/read-on-branch.ts +2 -2
  664. package/src/modules/{workflow/workflow-hooks.ts → version-control/version-control-hooks.ts} +3 -3
  665. package/src/modules/{workflow/workflow.routes.ts → version-control/version-control.routes.ts} +64 -63
  666. package/src/modules/{workflow/workflow.service.ts → version-control/version-control.service.ts} +92 -97
  667. package/src/modules/workspace/__tests__/agent-access.gate.test.ts +8 -8
  668. package/src/modules/workspace/__tests__/agent-downloads.test.ts +13 -16
  669. package/src/modules/workspace/__tests__/agent-uploads.test.ts +11 -12
  670. package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +11 -16
  671. package/src/modules/workspace/__tests__/file-stat-access.test.ts +3 -3
  672. package/src/modules/workspace/__tests__/git-internals.security.test.ts +27 -27
  673. package/src/modules/workspace/__tests__/kb-repository.test.ts +1 -1
  674. package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +3 -3
  675. package/src/modules/workspace/__tests__/start-session.default-branch.test.ts +152 -0
  676. package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +6 -6
  677. package/src/modules/workspace/__tests__/workspace-tmp.tools.test.ts +13 -14
  678. package/src/modules/workspace/__tests__/workspace.routes.at-ref.test.ts +6 -6
  679. package/src/modules/workspace/__tests__/workspace.routes.branch-errors.test.ts +4 -4
  680. package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +6 -6
  681. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +8 -8
  682. package/src/modules/workspace/__tests__/workspace.routes.download-implies-read.test.ts +4 -4
  683. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +6 -6
  684. package/src/modules/workspace/__tests__/workspace.routes.file-writes.test.ts +6 -6
  685. package/src/modules/workspace/__tests__/workspace.routes.folder-zip-access.test.ts +4 -4
  686. package/src/modules/workspace/__tests__/workspace.routes.folders.test.ts +6 -6
  687. package/src/modules/workspace/__tests__/workspace.routes.move-platform-files.test.ts +7 -7
  688. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +13 -13
  689. package/src/modules/workspace/__tests__/workspace.service.any-workspace-credentials.test.ts +1 -1
  690. package/src/modules/workspace/__tests__/workspace.service.replaced-repository.test.ts +1 -1
  691. package/src/modules/workspace/__tests__/workspace.service.worktrees.test.ts +1 -1
  692. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +5 -5
  693. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +5 -5
  694. package/src/modules/workspace/__tests__/workspace.tools.test.ts +27 -27
  695. package/src/modules/workspace/agent-access.gate.ts +5 -5
  696. package/src/modules/workspace/git-internals.middleware.ts +2 -2
  697. package/src/modules/workspace/kb-repository.ts +1 -1
  698. package/src/modules/workspace/startup/__tests__/kb-startup-runner.degraded.test.ts +1 -1
  699. package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +1 -1
  700. package/src/modules/workspace/startup/__tests__/kb-startup-runner.worktrees.test.ts +1 -1
  701. package/src/modules/workspace/startup/kb-git.ts +1 -1
  702. package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +94 -70
  703. package/src/modules/workspace/startup/steps/seed-tree.ts +4 -5
  704. package/src/modules/workspace/startup/steps/template-files.step.ts +4 -5
  705. package/src/modules/workspace/startup/steps/template-source.ts +21 -6
  706. package/src/modules/workspace/workspace-tmp.ts +4 -4
  707. package/src/modules/workspace/workspace.routes.ts +26 -26
  708. package/src/modules/workspace/workspace.service.ts +11 -11
  709. package/src/modules/workspace/workspace.tools.ts +17 -10
  710. package/src/shared/__tests__/canonical-file-identity.test.ts +5 -5
  711. package/src/shared/__tests__/http-errors.test.ts +4 -4
  712. package/src/shared/canonical-file-identity.ts +5 -5
  713. package/src/shared/domain-errors.ts +44 -44
  714. package/src/shared/git.contract.ts +1 -1
  715. package/src/shared/http-errors.ts +3 -3
  716. package/src/shared/kb-context.ts +130 -130
  717. package/src/shared/pending-proposals.ts +4 -4
  718. package/src/tenancy/__tests__/tenant-host.test.ts +372 -372
  719. package/src/tenancy/__tests__/tenant-runtime.test.ts +221 -221
  720. package/src/tenancy/__tests__/tenant-secrets.test.ts +35 -35
  721. package/src/tenancy/index.ts +35 -35
  722. package/src/tenancy/static-tenant-source.ts +1 -1
  723. package/src/tenancy/tenant-host.ts +261 -261
  724. package/src/tenancy/tenant-runtime.ts +208 -208
  725. package/src/tenancy/tenant-source.contract.ts +59 -59
  726. package/dist/modules/workflow/agent-tools/change-request-read-shape.d.ts.map +0 -1
  727. package/dist/modules/workflow/agent-tools/change-request-read-shape.js.map +0 -1
  728. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +0 -1
  729. package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +0 -1
  730. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +0 -1
  731. package/dist/modules/workflow/agent-tools/change-request-summary.js.map +0 -1
  732. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +0 -1
  733. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +0 -1
  734. package/dist/modules/workflow/change-request-hooks.d.ts.map +0 -1
  735. package/dist/modules/workflow/change-request-hooks.js.map +0 -1
  736. package/dist/modules/workflow/event-bus.d.ts.map +0 -1
  737. package/dist/modules/workflow/event-bus.js.map +0 -1
  738. package/dist/modules/workflow/events-delivery.d.ts.map +0 -1
  739. package/dist/modules/workflow/events-delivery.js.map +0 -1
  740. package/dist/modules/workflow/events.routes.d.ts.map +0 -1
  741. package/dist/modules/workflow/events.routes.js.map +0 -1
  742. package/dist/modules/workflow/file-lock.service.d.ts.map +0 -1
  743. package/dist/modules/workflow/file-lock.service.js.map +0 -1
  744. package/dist/modules/workflow/git/change-request-link.d.ts.map +0 -1
  745. package/dist/modules/workflow/git/change-request-link.js.map +0 -1
  746. package/dist/modules/workflow/git/git-version.d.ts.map +0 -1
  747. package/dist/modules/workflow/git/git-version.js.map +0 -1
  748. package/dist/modules/workflow/git/git.service.d.ts.map +0 -1
  749. package/dist/modules/workflow/git/git.service.js.map +0 -1
  750. package/dist/modules/workflow/git/merge-commit.d.ts.map +0 -1
  751. package/dist/modules/workflow/git/merge-commit.js.map +0 -1
  752. package/dist/modules/workflow/git/node-git-runner.d.ts.map +0 -1
  753. package/dist/modules/workflow/git/node-git-runner.js.map +0 -1
  754. package/dist/modules/workflow/git/pull-request.service.d.ts.map +0 -1
  755. package/dist/modules/workflow/git/pull-request.service.js.map +0 -1
  756. package/dist/modules/workflow/index.d.ts.map +0 -1
  757. package/dist/modules/workflow/index.js.map +0 -1
  758. package/dist/modules/workflow/pending-commits.service.d.ts.map +0 -1
  759. package/dist/modules/workflow/pending-commits.service.js.map +0 -1
  760. package/dist/modules/workflow/pending-commits.worker.d.ts.map +0 -1
  761. package/dist/modules/workflow/pending-commits.worker.js.map +0 -1
  762. package/dist/modules/workflow/read-on-branch.d.ts.map +0 -1
  763. package/dist/modules/workflow/read-on-branch.js.map +0 -1
  764. package/dist/modules/workflow/recovery-bot.d.ts.map +0 -1
  765. package/dist/modules/workflow/recovery-bot.js.map +0 -1
  766. package/dist/modules/workflow/review-workflow/approval-lock.d.ts.map +0 -1
  767. package/dist/modules/workflow/review-workflow/approval-lock.js.map +0 -1
  768. package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts.map +0 -1
  769. package/dist/modules/workflow/review-workflow/review-workflow.interface.js +0 -2
  770. package/dist/modules/workflow/review-workflow/review-workflow.interface.js.map +0 -1
  771. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +0 -1
  772. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +0 -1
  773. package/dist/modules/workflow/sanitize-error.d.ts.map +0 -1
  774. package/dist/modules/workflow/sanitize-error.js.map +0 -1
  775. package/dist/modules/workflow/workflow-hooks.d.ts.map +0 -1
  776. package/dist/modules/workflow/workflow-hooks.js.map +0 -1
  777. package/dist/modules/workflow/workflow.routes.d.ts.map +0 -1
  778. package/dist/modules/workflow/workflow.routes.js.map +0 -1
  779. package/dist/modules/workflow/workflow.service.d.ts.map +0 -1
  780. package/dist/modules/workflow/workflow.service.js.map +0 -1
  781. package/src/modules/mcp/__tests__/read-only-hint.e2e.test.ts +0 -213
  782. /package/dist/modules/{workflow → version-control}/agent-tools/change-request-read-shape.d.ts +0 -0
  783. /package/dist/modules/{workflow → version-control}/agent-tools/change-request-read-shape.js +0 -0
  784. /package/dist/modules/{workflow → version-control}/agent-tools/change-request-read.tools.d.ts +0 -0
  785. /package/dist/modules/{workflow → version-control}/agent-tools/change-request-summary.d.ts +0 -0
  786. /package/dist/modules/{workflow → version-control}/agent-tools/change-request-summary.js +0 -0
  787. /package/dist/modules/{workflow → version-control}/change-request-hooks.js +0 -0
  788. /package/dist/modules/{workflow/review-workflow → version-control/change-request-review}/approval-lock.d.ts +0 -0
  789. /package/dist/modules/{workflow/review-workflow → version-control/change-request-review}/approval-lock.js +0 -0
  790. /package/dist/modules/{workflow → version-control}/events-delivery.js +0 -0
  791. /package/dist/modules/{workflow → version-control}/events.routes.js +0 -0
  792. /package/dist/modules/{workflow → version-control}/git/change-request-link.d.ts +0 -0
  793. /package/dist/modules/{workflow → version-control}/git/change-request-link.js +0 -0
  794. /package/dist/modules/{workflow → version-control}/git/git-version.d.ts +0 -0
  795. /package/dist/modules/{workflow → version-control}/git/git-version.js +0 -0
  796. /package/dist/modules/{workflow → version-control}/git/merge-commit.d.ts +0 -0
  797. /package/dist/modules/{workflow → version-control}/git/merge-commit.js +0 -0
  798. /package/dist/modules/{workflow → version-control}/git/node-git-runner.d.ts +0 -0
  799. /package/dist/modules/{workflow → version-control}/recovery-bot.d.ts +0 -0
  800. /package/dist/modules/{workflow → version-control}/recovery-bot.js +0 -0
  801. /package/dist/modules/{workflow → version-control}/sanitize-error.d.ts +0 -0
  802. /package/dist/modules/{workflow → version-control}/sanitize-error.js +0 -0
  803. /package/src/modules/{workflow → version-control}/__tests__/change-request-hooks.test.ts +0 -0
  804. /package/src/modules/{workflow → version-control}/__tests__/fake-file-lock-db.ts +0 -0
  805. /package/src/modules/{workflow → version-control}/__tests__/pending-commits.canonical.test.ts +0 -0
  806. /package/src/modules/{workflow → version-control}/__tests__/pending-commits.enqueue-if-absent.test.ts +0 -0
  807. /package/src/modules/{workflow → version-control}/__tests__/pending-commits.repository-replaced.test.ts +0 -0
  808. /package/src/modules/{workflow → version-control}/__tests__/sanitize-error.test.ts +0 -0
  809. /package/src/modules/{workflow → version-control}/agent-tools/__tests__/change-request-read-shape.test.ts +0 -0
  810. /package/src/modules/{workflow → version-control}/agent-tools/change-request-read-shape.ts +0 -0
  811. /package/src/modules/{workflow → version-control}/agent-tools/change-request-summary.ts +0 -0
  812. /package/src/modules/{workflow/review-workflow → version-control/change-request-review}/approval-lock.ts +0 -0
  813. /package/src/modules/{workflow → version-control}/git/__tests__/branchAuthor.test.ts +0 -0
  814. /package/src/modules/{workflow → version-control}/git/__tests__/change-request-link.test.ts +0 -0
  815. /package/src/modules/{workflow → version-control}/git/__tests__/git-version.failure.test.ts +0 -0
  816. /package/src/modules/{workflow → version-control}/git/__tests__/git-version.test.ts +0 -0
  817. /package/src/modules/{workflow → version-control}/git/__tests__/node-git-runner.test.ts +0 -0
  818. /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.getPrDetail.test.ts +0 -0
  819. /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.placeholder.test.ts +0 -0
  820. /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.viewer-can-cancel.test.ts +0 -0
  821. /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.viewer-can-delete.test.ts +0 -0
  822. /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.viewer-can-update.test.ts +0 -0
  823. /package/src/modules/{workflow → version-control}/git/change-request-link.ts +0 -0
  824. /package/src/modules/{workflow → version-control}/git/git-version.ts +0 -0
  825. /package/src/modules/{workflow → version-control}/git/merge-commit.ts +0 -0
  826. /package/src/modules/{workflow → version-control}/recovery-bot.ts +0 -0
  827. /package/src/modules/{workflow → version-control}/sanitize-error.ts +0 -0
@@ -5,7 +5,7 @@ import { logger } from '../../shared/logging.js';
5
5
  const log = logger('vault');
6
6
  import { and, desc, eq, inArray, isNull, like, or } from 'drizzle-orm';
7
7
  import type { Database } from '../database/index.js';
8
- import { secrets } from '../database/index.js';
8
+ import { pgErrorCode, secrets } from '../database/index.js';
9
9
  import { TokenCrypto } from '../../shared/token-crypto.js';
10
10
  import { assertSafeFetchUrl } from '../../shared/ssrf.js';
11
11
  import { readCapped } from '../../shared/read-capped.js';
@@ -24,10 +24,16 @@ import {
24
24
  type NamespaceSecretCount,
25
25
  isKeyInNamespace,
26
26
  type ForcedRefreshOutcome,
27
+ type ManagedSignInRef,
28
+ type ManagedTokenRefresher,
29
+ type ManagedTokenRevoker,
30
+ type DeliveredTokenSet,
31
+ type ManagedRowsWriter,
27
32
  InvalidSecretError,
28
33
  SecretNotFoundError,
29
34
  SecretOAuthError,
30
35
  } from './secrets-vault.contract.js';
36
+ import { timingSafeStringEqual } from '../auth/index.js';
31
37
 
32
38
  const MAX_KEY_LEN = 200;
33
39
  const MAX_LABEL_LEN = 200;
@@ -36,6 +42,31 @@ const MAX_VALUE_LEN = 100_000;
36
42
  const REFRESH_SKEW_MS = 60_000;
37
43
  /** Upper bound on a token-endpoint round-trip, so a hung provider can't block callers. */
38
44
  const TOKEN_REQUEST_TIMEOUT_MS = 10_000;
45
+ /** Upper bound on telling a provider a managed sign-in is over, so a deletion never waits on it. */
46
+ const REVOKE_TIMEOUT_MS = 10_000;
47
+ /** Upper bound on a broker renewing a managed token: past it the stale token is served and the next call asks again. */
48
+ const REFRESH_TIMEOUT_MS = 15_000;
49
+ /** How many times a guarded replacement re-reads a row that changed under its revoke before giving up. */
50
+ const REPLACE_ATTEMPTS = 3;
51
+ /** How many times a handoff re-reads a row a refresh rotated under it (the nonce rides along) before refusing. */
52
+ const HANDOFF_ATTEMPTS = 3;
53
+
54
+ /** `work`, or a rejection once `ms` have passed: a handler that never settles must not hold the vault. */
55
+ async function bounded<T>(work: Promise<T>, ms: number, what: string): Promise<T> {
56
+ let timer: ReturnType<typeof setTimeout> | undefined;
57
+ try {
58
+ return await Promise.race([
59
+ work,
60
+ new Promise<never>((_, reject) => {
61
+ timer = setTimeout(() => reject(new Error(`${what} did not settle within ${ms}ms`)), ms);
62
+ }),
63
+ ]);
64
+ } finally {
65
+ if (timer) clearTimeout(timer);
66
+ }
67
+ }
68
+ /** How often a managed sign-in's opening is retried against a row that keeps changing under it. */
69
+ const BEGIN_MANAGED_ATTEMPTS = 3;
39
70
  /** How much of a refusing token endpoint's body is read for its error code: an OAuth error object, never a whole error page. */
40
71
  const TOKEN_ERROR_BODY_MAX_BYTES = 2048;
41
72
  // Hexis's own sentences for a refused token request: what to change, in our
@@ -155,6 +186,14 @@ interface OAuthBlob {
155
186
  * like the verifier: consumed by the exchange it was minted for.
156
187
  */
157
188
  pendingScopes?: string;
189
+ /**
190
+ * A managed sign-in in progress: the one-time nonce the broker's handoff
191
+ * must echo. Stored encrypted like the verifier, and for the same reason —
192
+ * it binds the handoff to the sign-in this row opened, so a handoff that
193
+ * is replayed or forged lands nowhere. Cleared by the handoff it was
194
+ * minted for.
195
+ */
196
+ pendingNonce?: string;
158
197
  }
159
198
 
160
199
  /**
@@ -171,6 +210,20 @@ interface OAuthBlob {
171
210
  */
172
211
  export class DbSecretsVaultService implements ISecretsVaultService {
173
212
  private cryptoInstance: TokenCrypto | null = null;
213
+ /**
214
+ * How a MANAGED sign-in's tokens are renewed and revoked — through the
215
+ * broker and the provider respectively. Setter-injected: both depend on
216
+ * the connector catalog, which is built after this service. Without them a
217
+ * managed token that expires is served stale (a transient, retried on the
218
+ * next call) and a disconnect deletes without revoking.
219
+ */
220
+ private managedRefresher?: ManagedTokenRefresher;
221
+ private managedRevoker?: ManagedTokenRevoker;
222
+
223
+ setManagedTokenHandlers(handlers: { refresher?: ManagedTokenRefresher; revoker?: ManagedTokenRevoker }): void {
224
+ this.managedRefresher = handlers.refresher;
225
+ this.managedRevoker = handlers.revoker;
226
+ }
174
227
 
175
228
  constructor(
176
229
  private readonly db: Database,
@@ -246,15 +299,20 @@ export class DbSecretsVaultService implements ISecretsVaultService {
246
299
  const label = this.normaliseLabel(input.label);
247
300
  const valueEncrypted = this.crypto().encrypt(input.value);
248
301
 
302
+ // The caller's row may hold a managed sign-in: its tokens are told to the
303
+ // provider before this value replaces them, as on every replacement.
249
304
  // Upsert on (user_id, key): re-saving a key replaces its value in place.
250
- const [row] = await this.db
251
- .insert(secrets)
252
- .values({ userId, key, kind: 'static', label, valueEncrypted, oauthMeta: null })
253
- .onConflictDoUpdate({
254
- target: [secrets.userId, secrets.key],
255
- set: { kind: 'static', label, valueEncrypted, oauthMeta: null, updatedAt: new Date() },
256
- })
257
- .returning();
305
+ const row = await this.revokedReplacement(and(eq(secrets.userId, userId), eq(secrets.key, key)), (guard) =>
306
+ this.db
307
+ .insert(secrets)
308
+ .values({ userId, key, kind: 'static', label, valueEncrypted, oauthMeta: null })
309
+ .onConflictDoUpdate({
310
+ target: [secrets.userId, secrets.key],
311
+ set: { kind: 'static', label, valueEncrypted, oauthMeta: null, updatedAt: new Date() },
312
+ ...(guard ? { setWhere: guard } : {}),
313
+ })
314
+ .returning(),
315
+ );
258
316
  this.notifyMutation(userId);
259
317
  return this.toSummary(row);
260
318
  }
@@ -269,26 +327,28 @@ export class DbSecretsVaultService implements ISecretsVaultService {
269
327
  const valueEncrypted = this.crypto().encrypt(JSON.stringify(blob));
270
328
  const oauthMeta = this.publicProviderMeta(provider);
271
329
 
272
- const [row] = await this.db
273
- .insert(secrets)
274
- .values({ userId, key, kind: 'oauth', label, valueEncrypted, oauthMeta })
275
- .onConflictDoUpdate({
276
- target: [secrets.userId, secrets.key],
277
- // Replacing an oauth secret's provider config drops any existing tokens
278
- // (they belonged to the old client) — the user must re-authorize.
279
- set: { kind: 'oauth', label, valueEncrypted, oauthMeta, updatedAt: new Date() },
280
- })
281
- .returning();
330
+ // A managed sign-in under this key loses its tokens here: the provider is
331
+ // told first, as on every replacement.
332
+ const row = await this.revokedReplacement(and(eq(secrets.userId, userId), eq(secrets.key, key)), (guard) =>
333
+ this.db
334
+ .insert(secrets)
335
+ .values({ userId, key, kind: 'oauth', label, valueEncrypted, oauthMeta })
336
+ .onConflictDoUpdate({
337
+ target: [secrets.userId, secrets.key],
338
+ // Replacing an oauth secret's provider config drops any existing tokens
339
+ // (they belonged to the old client) — the user must re-authorize.
340
+ set: { kind: 'oauth', label, valueEncrypted, oauthMeta, updatedAt: new Date() },
341
+ ...(guard ? { setWhere: guard } : {}),
342
+ })
343
+ .returning(),
344
+ );
282
345
  this.notifyMutation(userId);
283
346
  return this.toSummary(row);
284
347
  }
285
348
 
286
349
  async remove(userId: string, id: string): Promise<void> {
287
- const res = await this.db
288
- .delete(secrets)
289
- .where(and(eq(secrets.id, id), eq(secrets.userId, userId)))
290
- .returning({ id: secrets.id });
291
- if (res.length === 0) throw new SecretNotFoundError(id);
350
+ const gone = await this.revokeThenDelete(and(eq(secrets.id, id), eq(secrets.userId, userId)));
351
+ if (gone.length === 0) throw new SecretNotFoundError(id);
292
352
  this.notifyMutation(userId);
293
353
  }
294
354
 
@@ -303,37 +363,199 @@ export class DbSecretsVaultService implements ISecretsVaultService {
303
363
  const label = this.normaliseLabel(input.label);
304
364
  const valueEncrypted = this.crypto().encrypt(input.value);
305
365
 
366
+ // A shared row that held a managed workspace connection is told to its
367
+ // provider before this value replaces it; the tokens would otherwise go
368
+ // unrevoked with the row's kind.
306
369
  // Upsert the ONE shared row for this key (user_id NULL). The conflict target
307
370
  // is the partial unique index `secrets_shared_key_unq` — hence `targetWhere`.
308
- const [row] = await this.db
309
- .insert(secrets)
310
- .values({ userId: null, key, kind: 'static', label, valueEncrypted, oauthMeta: null })
311
- .onConflictDoUpdate({
312
- target: secrets.key,
313
- targetWhere: isNull(secrets.userId),
314
- set: { kind: 'static', label, valueEncrypted, oauthMeta: null, updatedAt: new Date() },
315
- })
316
- .returning();
371
+ const row = await this.revokedReplacement(and(isNull(secrets.userId), eq(secrets.key, key)), (guard) =>
372
+ this.db
373
+ .insert(secrets)
374
+ .values({ userId: null, key, kind: 'static', label, valueEncrypted, oauthMeta: null })
375
+ .onConflictDoUpdate({
376
+ target: secrets.key,
377
+ targetWhere: isNull(secrets.userId),
378
+ set: { kind: 'static', label, valueEncrypted, oauthMeta: null, updatedAt: new Date() },
379
+ ...(guard ? { setWhere: guard } : {}),
380
+ })
381
+ .returning(),
382
+ );
317
383
  this.notifyMutation(null);
318
384
  return this.toSummary(row);
319
385
  }
320
386
 
321
387
  async removeShared(key: string): Promise<void> {
322
- const res = await this.db
323
- .delete(secrets)
324
- .where(and(isNull(secrets.userId), eq(secrets.key, key)))
325
- .returning({ id: secrets.id });
326
- if (res.length === 0) throw new SecretNotFoundError(key);
388
+ const gone = await this.revokeThenDelete(and(isNull(secrets.userId), eq(secrets.key, key)));
389
+ if (gone.length === 0) throw new SecretNotFoundError(key);
327
390
  this.notifyMutation(null);
328
391
  }
329
392
 
330
393
  async removeUserByKey(userId: string, key: string): Promise<void> {
331
- const res = await this.db
332
- .delete(secrets)
333
- .where(and(eq(secrets.userId, userId), eq(secrets.key, key)))
334
- .returning({ id: secrets.id });
335
- if (res.length === 0) throw new SecretNotFoundError(key);
394
+ const gone = await this.revokeThenDelete(and(eq(secrets.userId, userId), eq(secrets.key, key)));
395
+ if (gone.length === 0) throw new SecretNotFoundError(key);
396
+ this.notifyMutation(userId);
397
+ }
398
+
399
+ /**
400
+ * Delete the rows `where` names, each told to its provider first when it
401
+ * is a managed sign-in with tokens (see {@link revokeRow}). The delete is
402
+ * guarded on the ciphertext the revoke read: a handoff or a refresh landing
403
+ * in between has replaced the tokens, and deleting THOSE unrevoked is the
404
+ * leak the revoke exists to prevent. A miss re-reads the row and goes
405
+ * again, bounded; what could not be settled in those passes is left
406
+ * standing rather than deleted blind.
407
+ */
408
+ private async revokeThenDelete(where: ReturnType<typeof and>): Promise<{ id: string; userId: string | null; kind: string }[]> {
409
+ // No revoker, nothing to read first: one plain delete, as every removal
410
+ // was before managed sign-ins, and no miss to re-read for.
411
+ if (!this.managedRevoker) {
412
+ return this.db.delete(secrets).where(where).returning({ id: secrets.id, userId: secrets.userId, kind: secrets.kind });
413
+ }
414
+ const gone: { id: string; userId: string | null; kind: string }[] = [];
415
+ for (let attempt = 0; attempt < 3; attempt++) {
416
+ const rows = await this.db.select().from(secrets).where(where);
417
+ if (rows.length === 0) break;
418
+ // Every row's revocation at once, so a bulk delete (a namespace, a
419
+ // person's connections) waits one revocation's bound, not one per row.
420
+ await Promise.all(rows.map((row) => this.revokeRow(row)));
421
+ let missed = false;
422
+ for (const row of rows) {
423
+ const deleted = await this.db
424
+ .delete(secrets)
425
+ .where(and(eq(secrets.id, row.id), eq(secrets.valueEncrypted, row.valueEncrypted)))
426
+ .returning({ id: secrets.id, userId: secrets.userId, kind: secrets.kind });
427
+ if (deleted.length === 0) missed = true;
428
+ else gone.push(...deleted);
429
+ }
430
+ if (!missed) break;
431
+ }
432
+ return gone;
433
+ }
434
+
435
+ async removeManagedRowsOfUser(userId: string, within?: ManagedRowsWriter): Promise<number> {
436
+ if (within) return this.removeManagedRowsLocked(userId, within);
437
+ // Every oauth row of the user whose meta names a managed app. Read
438
+ // first (the managed mark lives in JSON the WHERE cannot see), revoked,
439
+ // then deleted by id; the per-user filter on the delete is what keeps a
440
+ // row read for this user from ever deleting another's.
441
+ const rows = await this.db
442
+ .select()
443
+ .from(secrets)
444
+ .where(and(eq(secrets.userId, userId), eq(secrets.kind, 'oauth')));
445
+ const managed = rows.filter((r) => this.managedRefOf(r.oauthMeta) !== null);
446
+ if (managed.length === 0) return 0;
447
+ const deleted = await this.revokeThenDelete(and(eq(secrets.userId, userId), inArray(secrets.id, managed.map((r) => r.id))));
448
+ if (deleted.length > 0) this.notifyMutation(userId);
449
+ // A row the guarded delete could not settle (its tokens kept changing
450
+ // under the revoke) is still here, unrevoked. The caller is told rather
451
+ // than left to believe the person holds nothing: an erasure refuses and
452
+ // is retried, a switch-off logs it and the row is refused on use.
453
+ if (deleted.length < managed.length) {
454
+ throw new SecretOAuthError(
455
+ `${managed.length - deleted.length} managed sign-in(s) kept changing while being revoked and were not removed; try again`,
456
+ );
457
+ }
458
+ return deleted.length;
459
+ }
460
+
461
+ /**
462
+ * The same removal inside a caller's transaction (an account erasure, just
463
+ * before the user row goes): the rows are LOCKED for the transaction, so a
464
+ * handoff or a refresh arriving now waits and then finds no row, which is
465
+ * what makes the revoke final. No guard is needed under the lock; the
466
+ * revoke stays bounded (see {@link revokeRow}), so the transaction never
467
+ * waits on a provider longer than that.
468
+ */
469
+ private async removeManagedRowsLocked(userId: string, tx: ManagedRowsWriter): Promise<number> {
470
+ const rows = await tx
471
+ .select()
472
+ .from(secrets)
473
+ .where(and(eq(secrets.userId, userId), eq(secrets.kind, 'oauth')))
474
+ .for('update');
475
+ const managed = rows.filter((r) => this.managedRefOf(r.oauthMeta) !== null);
476
+ if (managed.length === 0) return 0;
477
+ await Promise.all(managed.map((row) => this.revokeRow(row)));
478
+ await tx.delete(secrets).where(
479
+ and(
480
+ eq(secrets.userId, userId),
481
+ inArray(
482
+ secrets.id,
483
+ managed.map((r) => r.id),
484
+ ),
485
+ ),
486
+ );
336
487
  this.notifyMutation(userId);
488
+ return managed.length;
489
+ }
490
+
491
+ /**
492
+ * Replace what `where` names with `write`, a managed sign-in's tokens told
493
+ * to their provider first (see {@link revokeRow}; best effort, never holding
494
+ * the user's decision). The write is guarded on the ciphertext the revoke
495
+ * read (`setWhere`): a refresh or a handoff landing in between has rotated
496
+ * the tokens, and writing over THOSE unrevoked is the leak the revoke exists
497
+ * to prevent — as the guarded delete of {@link revokeThenDelete}. A miss
498
+ * re-reads and goes again, bounded; a row that kept changing is left as it
499
+ * is and the caller is told. With no revoker wired there is nothing to read
500
+ * first: one plain write, as every replacement was before managed sign-ins.
501
+ */
502
+ private async revokedReplacement<T>(
503
+ where: ReturnType<typeof and>,
504
+ write: (guard?: ReturnType<typeof eq>) => Promise<T[]>,
505
+ ): Promise<T> {
506
+ if (!this.managedRevoker) return (await write())[0] as T;
507
+ for (let attempt = 0; attempt < REPLACE_ATTEMPTS; attempt++) {
508
+ const rows = await this.db.select().from(secrets).where(where);
509
+ await Promise.all(rows.map((row) => this.revokeRow(row)));
510
+ // The row as it was read, or no row at all: a conflict with anything
511
+ // else (a row written meanwhile) is a miss, and the next pass reads it.
512
+ const guard =
513
+ rows.length > 0
514
+ ? inArray(
515
+ secrets.valueEncrypted,
516
+ rows.map((row) => row.valueEncrypted),
517
+ )
518
+ : eq(secrets.valueEncrypted, '');
519
+ const written = await write(guard);
520
+ if (written.length > 0) return written[0] as T;
521
+ }
522
+ throw new SecretOAuthError('the sign-in kept changing while being replaced; try again');
523
+ }
524
+
525
+ private async revokeRow(row: typeof secrets.$inferSelect): Promise<void> {
526
+ if (!this.managedRevoker || row.kind !== 'oauth') return;
527
+ const ref = this.managedRefOf(row.oauthMeta);
528
+ if (!ref) return;
529
+ const tokens = this.readTokensSafe(row.valueEncrypted);
530
+ if (!tokens?.access_token) return;
531
+ // Bounded: the delete that follows is the user's decision, and a revoker
532
+ // that never settles must not hold it.
533
+ try {
534
+ await bounded(
535
+ this.managedRevoker.revoke(ref, { access_token: tokens.access_token, refresh_token: tokens.refresh_token }),
536
+ REVOKE_TIMEOUT_MS,
537
+ 'revocation',
538
+ );
539
+ } catch (err) {
540
+ // The key and the connector name the sign-in. The error itself stays
541
+ // out: a revoker's message is its own, and this line is the one place a
542
+ // token could otherwise reach a log from this path.
543
+ log.warn('revoking a managed sign-in with its provider failed; deleting it here anyway', {
544
+ key: row.key,
545
+ connector: ref.connectorId,
546
+ failure: err instanceof Error ? err.name : typeof err,
547
+ });
548
+ }
549
+ }
550
+
551
+ /** The managed reference a row's public meta carries, or null for an ordinary sign-in. */
552
+ private managedRefOf(raw: unknown): ManagedSignInRef | null {
553
+ const meta = (raw ?? {}) as Record<string, unknown>;
554
+ const m = meta.managed as Record<string, unknown> | undefined;
555
+ if (!m || typeof m !== 'object') return null;
556
+ const connectorId = typeof m.connectorId === 'string' ? m.connectorId : '';
557
+ const scopeSet = typeof m.scopeSet === 'string' ? m.scopeSet : '';
558
+ return connectorId && scopeSet ? { connectorId, scopeSet } : null;
337
559
  }
338
560
 
339
561
  async countNamespace(prefix: string): Promise<NamespaceSecretCount> {
@@ -356,10 +578,10 @@ export class DbSecretsVaultService implements ISecretsVaultService {
356
578
  for (let pass = 0; pass < 5; pass++) {
357
579
  const rows = await this.namespaceRows(prefix);
358
580
  if (rows.length === 0) break;
359
- const deleted = await this.db
360
- .delete(secrets)
361
- .where(inArray(secrets.id, rows.map((r) => r.id)))
362
- .returning({ userId: secrets.userId, kind: secrets.kind });
581
+ // A managed sign-in under the namespace is told to its provider
582
+ // before the delete, for the same reason the per-row removals do —
583
+ // and guarded the same way, when a revoker is there to need it.
584
+ const deleted = await this.revokeThenDelete(inArray(secrets.id, rows.map((r) => r.id)));
363
585
  gone.push(...deleted);
364
586
  }
365
587
  } finally {
@@ -409,6 +631,7 @@ export class DbSecretsVaultService implements ISecretsVaultService {
409
631
  userId: secrets.userId,
410
632
  kind: secrets.kind,
411
633
  valueEncrypted: secrets.valueEncrypted,
634
+ oauthMeta: secrets.oauthMeta,
412
635
  })
413
636
  .from(secrets)
414
637
  .where(and(inArray(secrets.key, keys), or(isNull(secrets.userId), eq(secrets.userId, userId))));
@@ -417,6 +640,15 @@ export class DbSecretsVaultService implements ISecretsVaultService {
417
640
  const adminRow = rows.find((r) => r.key === key && r.userId === null);
418
641
  // Decrypt+parse the oauth row's token set once, then derive both fields from it.
419
642
  const tokens = userRow?.kind === 'oauth' ? this.readTokensSafe(userRow.valueEncrypted) : undefined;
643
+ // The shared row holds tokens only for a managed workspace connection;
644
+ // an ordinary sign-in's shared row is the client secret and reads as
645
+ // "no tokens" here, which is the truthful answer for it.
646
+ const adminTokens = adminRow?.kind === 'oauth' ? this.readTokensSafe(adminRow.valueEncrypted) : undefined;
647
+ const adminMeta = (adminRow?.oauthMeta ?? null) as Record<string, unknown> | null;
648
+ const accountOf = (meta: unknown): string | undefined => {
649
+ const a = (meta as Record<string, unknown> | null)?.account;
650
+ return typeof a === 'string' && a ? a : undefined;
651
+ };
420
652
  return {
421
653
  key,
422
654
  adminConfigured: !!adminRow,
@@ -430,10 +662,157 @@ export class DbSecretsVaultService implements ISecretsVaultService {
430
662
  // Only meaningful for an oauth row: the scopes the caller's token was granted,
431
663
  // so a coverage check can tell whether it still covers the tool's live scopes.
432
664
  grantedScopes: userRow?.kind === 'oauth' ? tokens?.scope : undefined,
665
+ adminAuthorized: adminRow?.kind === 'oauth' ? Boolean(adminTokens?.access_token) : undefined,
666
+ adminGrantedScopes: adminRow?.kind === 'oauth' ? adminTokens?.scope : undefined,
667
+ userAccount: userRow?.kind === 'oauth' ? accountOf(userRow.oauthMeta) : undefined,
668
+ adminAccount: adminRow?.kind === 'oauth' ? accountOf(adminMeta) : undefined,
669
+ // An owner's own app on the shared row: a client id without the managed mark.
670
+ adminBringsOwnApp:
671
+ adminRow?.kind === 'oauth'
672
+ ? this.managedRefOf(adminMeta) === null && typeof adminMeta?.clientId === 'string' && adminMeta.clientId !== ''
673
+ : undefined,
433
674
  };
434
675
  });
435
676
  }
436
677
 
678
+ async beginManagedConnection(input: {
679
+ userId: string | null;
680
+ key: string;
681
+ provider: OAuthProviderConfig;
682
+ nonce: string;
683
+ }): Promise<void> {
684
+ const key = this.requireKey(input.key);
685
+ const userId = input.userId === null ? null : this.requireUserId(input.userId);
686
+ const ref = input.provider.managed;
687
+ if (!ref) throw new InvalidSecretError('a managed sign-in needs its connector reference');
688
+ if (typeof input.nonce !== 'string' || input.nonce.length < 16) throw new InvalidSecretError('nonce is required');
689
+ const oauthMeta = this.publicProviderMeta(this.requireProvider(input.provider, { managed: true }));
690
+ const where = userId === null ? and(isNull(secrets.userId), eq(secrets.key, key)) : and(eq(secrets.userId, userId), eq(secrets.key, key));
691
+ // Read-modify-write on a LIVE credential: a refresh running concurrently
692
+ // may rotate the tokens between the read and the write, and writing our
693
+ // copy over them would leave the row holding a dead refresh token. So the
694
+ // update is guarded on the ciphertext read, and a miss re-reads and
695
+ // rebuilds from the fresh row; bounded, so a row that keeps changing
696
+ // fails loudly rather than forever. The same rule `beginOAuth` follows.
697
+ for (let attempt = 0; attempt < BEGIN_MANAGED_ATTEMPTS; attempt++) {
698
+ const [existing] = await this.db.select().from(secrets).where(where).limit(1);
699
+ // Keep what the SAME connection already holds (its tokens, so an
700
+ // abandoned reconnect costs nothing). The same connection means the same
701
+ // connector and scope set: a row of another connector's, or of another
702
+ // set's, holds a token for something else, and a declaration that
703
+ // changed must not hand it to the new one. Start clean over anything
704
+ // else too — a static value or an owner-app sign-in under this key.
705
+ const storedRef = existing?.kind === 'oauth' ? this.managedRefOf(existing.oauthMeta) : null;
706
+ const sameConnection =
707
+ storedRef !== null && storedRef.connectorId === ref.connectorId && storedRef.scopeSet === ref.scopeSet;
708
+ let kept: OAuthBlob = {};
709
+ if (existing && sameConnection) {
710
+ try {
711
+ kept = this.readBlob(existing.valueEncrypted);
712
+ } catch {
713
+ kept = {};
714
+ }
715
+ }
716
+ const next: OAuthBlob = { tokens: kept.tokens, pendingNonce: input.nonce };
717
+ const valueEncrypted = this.crypto().encrypt(JSON.stringify(next));
718
+ const account = sameConnection ? (existing!.oauthMeta as Record<string, unknown> | null)?.account : undefined;
719
+ const meta = typeof account === 'string' && account ? { ...oauthMeta, account } : oauthMeta;
720
+ if (!existing) {
721
+ try {
722
+ await this.db.insert(secrets).values({ userId, key, kind: 'oauth', label: null, valueEncrypted, oauthMeta: meta });
723
+ return;
724
+ } catch (err) {
725
+ // Another start of the same sign-in inserted the row first: the
726
+ // next pass finds it and updates it, as it would any existing row.
727
+ if (pgErrorCode(err) === '23505') continue; // unique_violation
728
+ throw err;
729
+ }
730
+ }
731
+ // A row that held ANOTHER connection's tokens is about to lose them:
732
+ // the provider is told first, as on every other replacement.
733
+ if (!sameConnection && storedRef) await this.revokeRow(existing);
734
+ const written = await this.db
735
+ .update(secrets)
736
+ .set({ kind: 'oauth', valueEncrypted, oauthMeta: meta, updatedAt: new Date() })
737
+ .where(and(eq(secrets.id, existing.id), eq(secrets.valueEncrypted, existing.valueEncrypted)))
738
+ .returning({ id: secrets.id });
739
+ if (Array.isArray(written) && written.length === 0) continue;
740
+ // A row that held something ELSE (a value, another connection's
741
+ // tokens, an owner's app) no longer does: caches keyed on it are told.
742
+ // The same connection's tokens carried over unchanged tell nobody.
743
+ if (!kept.tokens) this.notifyMutation(userId);
744
+ return;
745
+ }
746
+ throw new SecretOAuthError('The sign-in could not be opened: its stored credential kept changing. Try again.');
747
+ }
748
+
749
+ async completeManagedConnection(input: {
750
+ userId: string | null;
751
+ key: string;
752
+ nonce: string;
753
+ tokens: DeliveredTokenSet;
754
+ account?: string;
755
+ }): Promise<void> {
756
+ const key = this.requireKey(input.key);
757
+ const userId = input.userId === null ? null : this.requireUserId(input.userId);
758
+ const where = userId === null ? and(isNull(secrets.userId), eq(secrets.key, key)) : and(eq(secrets.userId, userId), eq(secrets.key, key));
759
+ // Every refusal below is the same words: a caller that can tell "no such
760
+ // sign-in" from "wrong nonce" from "already used" has learned which rows
761
+ // exist, and the handoff route is reachable with the broker secret alone.
762
+ const refused = () => new SecretOAuthError('No managed sign-in is waiting for this handoff');
763
+ const tokens = input.tokens;
764
+ if (typeof tokens?.access_token !== 'string' || !tokens.access_token) {
765
+ throw new InvalidSecretError('the handoff carries no access token');
766
+ }
767
+ // Read, check the nonce, write guarded on what was read; and again when
768
+ // the write misses. A miss with the nonce still on the row is a refresh
769
+ // that rotated the row's tokens meanwhile (a refresh carries the nonce
770
+ // across, see `inFlightOf`), not a second handoff: the broker delivered
771
+ // these tokens once, and refusing them would lose a valid sign-in for
772
+ // good. A row whose nonce is gone or changed refuses on the re-read.
773
+ for (let attempt = 0; ; attempt++) {
774
+ const [row] = await this.db.select().from(secrets).where(where).limit(1);
775
+ if (!row || row.kind !== 'oauth' || !this.managedRefOf(row.oauthMeta)) throw refused();
776
+ let blob: OAuthBlob;
777
+ try {
778
+ blob = this.readBlob(row.valueEncrypted);
779
+ } catch {
780
+ throw refused();
781
+ }
782
+ if (!blob.pendingNonce || typeof input.nonce !== 'string' || !timingSafeStringEqual(input.nonce, blob.pendingNonce)) {
783
+ throw refused();
784
+ }
785
+ const meta = { ...((row.oauthMeta ?? {}) as Record<string, unknown>) };
786
+ // A broker that echoes no `scope` delivered what the sign-in asked for:
787
+ // the scopes the row was opened with stand in, as `pendingScopes` does
788
+ // for the code exchange. Stored as unknown, the grant would count as
789
+ // covering nothing, and the sign-in would read as under-scoped for good.
790
+ // An EMPTY scope is not "none echoed": it is an empty grant, kept as such.
791
+ const requested = Array.isArray(meta.scopes) && meta.scopes.length > 0 ? meta.scopes.map(String).join(' ') : undefined;
792
+ const stored: OAuthTokenSet = {
793
+ access_token: tokens.access_token,
794
+ refresh_token: typeof tokens.refresh_token === 'string' ? tokens.refresh_token : undefined,
795
+ expires_at: typeof tokens.expires_at === 'number' ? tokens.expires_at : undefined,
796
+ token_type: typeof tokens.token_type === 'string' ? tokens.token_type : undefined,
797
+ scope: typeof tokens.scope === 'string' ? tokens.scope : requested,
798
+ };
799
+ // The nonce is one-time: it leaves with the handoff it was minted for,
800
+ // so the same handoff presented twice finds nothing to match.
801
+ const next: OAuthBlob = { tokens: stored };
802
+ if (typeof input.account === 'string' && input.account.trim()) meta.account = input.account.trim().slice(0, MAX_LABEL_LEN);
803
+ // Guarded on the ciphertext read above: two handoffs racing for one
804
+ // nonce cannot both land, whichever the provider sent first wins.
805
+ const landed = await this.db
806
+ .update(secrets)
807
+ .set({ valueEncrypted: this.crypto().encrypt(JSON.stringify(next)), oauthMeta: meta, updatedAt: new Date() })
808
+ .where(and(eq(secrets.id, row.id), eq(secrets.valueEncrypted, row.valueEncrypted)))
809
+ .returning({ id: secrets.id });
810
+ if (!(Array.isArray(landed) && landed.length === 0)) break;
811
+ if (attempt + 1 >= HANDOFF_ATTEMPTS) throw refused();
812
+ }
813
+ this.notifyMutation(userId);
814
+ }
815
+
437
816
  /**
438
817
  * Decrypt+parse an oauth row's token set once, or undefined if the blob can't be
439
818
  * read — a corrupted row or rotated encKey must degrade to "not authorized", not throw.
@@ -571,14 +950,20 @@ export class DbSecretsVaultService implements ISecretsVaultService {
571
950
  const blob: OAuthBlob = { clientSecret: provider.clientSecret };
572
951
  const valueEncrypted = this.crypto().encrypt(JSON.stringify(blob));
573
952
  const oauthMeta = this.publicProviderMeta(provider);
574
- await this.db
575
- .insert(secrets)
576
- .values({ userId: null, key, kind: 'oauth', label: null, valueEncrypted, oauthMeta })
577
- .onConflictDoUpdate({
578
- target: secrets.key,
579
- targetWhere: isNull(secrets.userId),
580
- set: { kind: 'oauth', valueEncrypted, oauthMeta, updatedAt: new Date() },
581
- });
953
+ // An owner's own app replacing a managed workspace connection: the
954
+ // connection's tokens are revoked with the provider before they go.
955
+ await this.revokedReplacement(and(isNull(secrets.userId), eq(secrets.key, key)), (guard) =>
956
+ this.db
957
+ .insert(secrets)
958
+ .values({ userId: null, key, kind: 'oauth', label: null, valueEncrypted, oauthMeta })
959
+ .onConflictDoUpdate({
960
+ target: secrets.key,
961
+ targetWhere: isNull(secrets.userId),
962
+ set: { kind: 'oauth', valueEncrypted, oauthMeta, updatedAt: new Date() },
963
+ ...(guard ? { setWhere: guard } : {}),
964
+ })
965
+ .returning({ id: secrets.id }),
966
+ );
582
967
  this.notifyMutation(null);
583
968
  }
584
969
 
@@ -616,8 +1001,11 @@ export class DbSecretsVaultService implements ISecretsVaultService {
616
1001
  }
617
1002
  // The save's own validation (https, no private or local address), BEFORE
618
1003
  // the caller's row is written or a URL built: a declaration that fails it
619
- // sends nothing anywhere.
620
- meta = this.requireProvider({ ...input.provider, publicClient: false });
1004
+ // sends nothing anywhere. A MANAGED declaration names no client of its
1005
+ // own; reaching here means an owner pinned the deployment's own app on
1006
+ // the shared row, and that app's id is the one the flow runs with.
1007
+ const clientId = input.provider.clientId || this.readMeta(shared.oauthMeta).clientId;
1008
+ meta = this.requireProvider({ ...input.provider, clientId, publicClient: false });
621
1009
  oauthMeta = this.publicProviderMeta(meta);
622
1010
  // A MOVED PROPERTY, decided on the ticket (OAuth-Sign-In-Follows-The-Live-
623
1011
  // Declaration, 2026-10-27): the secret used to be bound to the token
@@ -695,14 +1083,22 @@ export class DbSecretsVaultService implements ISecretsVaultService {
695
1083
  pendingScopes,
696
1084
  };
697
1085
  const valueEncrypted = this.crypto().encrypt(JSON.stringify(blob));
698
- const [row] = await this.db
699
- .insert(secrets)
700
- .values({ userId, key, kind: 'oauth', label: shared.label, valueEncrypted, oauthMeta })
701
- .onConflictDoUpdate({
702
- target: [secrets.userId, secrets.key],
703
- set: { kind: 'oauth', valueEncrypted, oauthMeta, updatedAt: new Date() },
704
- })
705
- .returning();
1086
+ // A managed sign-in on the caller's row (an owner pinned their own app
1087
+ // after the person connected through the broker) is replaced by this
1088
+ // start: its tokens are told to the provider first, and the write is
1089
+ // guarded on what the revoke read. Tokens carried over above were minted
1090
+ // under THIS configuration, never a managed one.
1091
+ const row = await this.revokedReplacement(and(eq(secrets.userId, userId), eq(secrets.key, key)), (guard) =>
1092
+ this.db
1093
+ .insert(secrets)
1094
+ .values({ userId, key, kind: 'oauth', label: shared.label, valueEncrypted, oauthMeta })
1095
+ .onConflictDoUpdate({
1096
+ target: [secrets.userId, secrets.key],
1097
+ set: { kind: 'oauth', valueEncrypted, oauthMeta, updatedAt: new Date() },
1098
+ ...(guard ? { setWhere: guard } : {}),
1099
+ })
1100
+ .returning(),
1101
+ );
706
1102
 
707
1103
  // Build the consent URL exactly as beginOAuth does, from the configuration
708
1104
  // this start uses, with the requested scopes chosen above.
@@ -777,10 +1173,16 @@ export class DbSecretsVaultService implements ISecretsVaultService {
777
1173
  }
778
1174
 
779
1175
  async forceRefresh(userId: string, key: string): Promise<ForcedRefreshOutcome> {
1176
+ // The caller's own row, or — for a variable the manual declares shared
1177
+ // (a managed workspace connection) — the one shared row every caller
1178
+ // resolves. Same tier rule as `resolve`, so a 401 against a shared
1179
+ // token repairs the row the call actually used.
1180
+ const scope = (await this.scopeOf?.(key)) ?? 'admin';
1181
+ const scopeFilter = scope === 'user' ? eq(secrets.userId, userId) : isNull(secrets.userId);
780
1182
  const [row] = await this.db
781
1183
  .select()
782
1184
  .from(secrets)
783
- .where(and(eq(secrets.userId, userId), eq(secrets.key, key)))
1185
+ .where(and(scopeFilter, eq(secrets.key, key)))
784
1186
  .limit(1);
785
1187
  if (!row || row.kind !== 'oauth') return 'skipped';
786
1188
  let blob: OAuthBlob;
@@ -827,17 +1229,38 @@ export class DbSecretsVaultService implements ISecretsVaultService {
827
1229
  return this.wipeTokens(row, blob);
828
1230
  }
829
1231
  const meta = this.readMeta(row.oauthMeta);
830
- const body = new URLSearchParams({
831
- grant_type: 'refresh_token',
832
- refresh_token: tokens.refresh_token,
833
- client_id: meta.clientId,
834
- });
835
- if (blob.clientSecret) body.set('client_secret', blob.clientSecret);
836
- if (meta.resource) body.set('resource', meta.resource);
837
-
838
1232
  let refreshed: OAuthTokenSet;
839
1233
  try {
840
- refreshed = await this.tokenRequest(meta.tokenUrl, body);
1234
+ if (meta.managed) {
1235
+ // A managed sign-in renews through the broker, which holds the
1236
+ // client secret this deployment never sees. No broker wired is an
1237
+ // outage from here, not a dead grant: the stale token is served and
1238
+ // the next call asks again.
1239
+ if (!this.managedRefresher) return { outcome: 'transient' };
1240
+ // Bounded like a revocation: a broker that never answers is an outage
1241
+ // (the stale token is served) and must not hold every resolution.
1242
+ const delivered = await bounded(
1243
+ this.managedRefresher.refresh(meta.managed, tokens.refresh_token),
1244
+ REFRESH_TIMEOUT_MS,
1245
+ 'the broker refresh',
1246
+ );
1247
+ refreshed = {
1248
+ access_token: delivered.access_token,
1249
+ refresh_token: delivered.refresh_token,
1250
+ expires_at: delivered.expires_at,
1251
+ token_type: delivered.token_type,
1252
+ scope: delivered.scope,
1253
+ };
1254
+ } else {
1255
+ const body = new URLSearchParams({
1256
+ grant_type: 'refresh_token',
1257
+ refresh_token: tokens.refresh_token,
1258
+ client_id: meta.clientId,
1259
+ });
1260
+ if (blob.clientSecret) body.set('client_secret', blob.clientSecret);
1261
+ if (meta.resource) body.set('resource', meta.resource);
1262
+ refreshed = await this.tokenRequest(meta.tokenUrl, body);
1263
+ }
841
1264
  } catch (err) {
842
1265
  // A DEFINITIVE provider rejection (400/401 — invalid_grant and friends)
843
1266
  // means the grant is dead: no future refresh will revive it. Wipe the
@@ -859,7 +1282,12 @@ export class DbSecretsVaultService implements ISecretsVaultService {
859
1282
  : result.outcome === 'refreshed'
860
1283
  ? 'the row had been rotated by a concurrent refresh meanwhile; the rotated sign-in is kept'
861
1284
  : 'the row had been removed or replaced meanwhile; nothing wiped';
862
- log.warn(`refresh refused for "${row.key}", ${did}: ${err.message}`);
1285
+ // A managed row's refusal came from the refresher, whose words are its
1286
+ // own (a port may put the refresh token it was handed in them): only
1287
+ // the status it reported is logged. The vault's own token request
1288
+ // writes fixed sentences, which are safe to repeat.
1289
+ const why = meta.managed ? `the broker refused it (HTTP ${err.providerStatus ?? 'unknown'})` : err.message;
1290
+ log.warn(`refresh refused for "${row.key}", ${did}: ${why}`);
863
1291
  return result;
864
1292
  }
865
1293
  return { outcome: 'transient' };
@@ -871,7 +1299,10 @@ export class DbSecretsVaultService implements ISecretsVaultService {
871
1299
  // rule as the exchange: only an ABSENT field means "unchanged"; an echoed
872
1300
  // value, empty included, is the provider's word.
873
1301
  if (refreshed.scope === undefined) refreshed.scope = tokens.scope;
874
- const next: OAuthBlob = { clientSecret: blob.clientSecret, tokens: refreshed };
1302
+ // A sign-in in flight on this row (a managed reconnect waiting for its
1303
+ // handoff, a consent waiting for its exchange) survives the refresh: the
1304
+ // nonce or verifier it minted is what that handoff must match.
1305
+ const next: OAuthBlob = { clientSecret: blob.clientSecret, tokens: refreshed, ...this.inFlightOf(blob) };
875
1306
 
876
1307
  // Optimistic concurrency: only persist if the stored ciphertext is unchanged,
877
1308
  // so a concurrent refresh on the same row doesn't clobber (Microsoft pattern).
@@ -918,7 +1349,22 @@ export class DbSecretsVaultService implements ISecretsVaultService {
918
1349
  }
919
1350
  }
920
1351
 
921
- /** Drop a dead grant's token set, keeping the client secret. See {@link refreshRow}. */
1352
+ /**
1353
+ * What a row holds for a sign-in IN FLIGHT: the one-time nonce a managed
1354
+ * handoff must echo, the PKCE verifier and requested scopes a code exchange
1355
+ * must present. A refresh or a wipe rewrites the row's tokens, never these:
1356
+ * dropping them would refuse the handoff or the exchange that is about to
1357
+ * land, for a reason the person could not see.
1358
+ */
1359
+ private inFlightOf(blob: OAuthBlob): Pick<OAuthBlob, 'pendingNonce' | 'pendingVerifier' | 'pendingScopes'> {
1360
+ return {
1361
+ ...(blob.pendingNonce ? { pendingNonce: blob.pendingNonce } : {}),
1362
+ ...(blob.pendingVerifier ? { pendingVerifier: blob.pendingVerifier } : {}),
1363
+ ...(blob.pendingScopes ? { pendingScopes: blob.pendingScopes } : {}),
1364
+ };
1365
+ }
1366
+
1367
+ /** Drop a dead grant's token set, keeping the client secret and any sign-in in flight. See {@link refreshRow}. */
922
1368
  private async wipeTokens(
923
1369
  row: typeof secrets.$inferSelect,
924
1370
  blob: OAuthBlob,
@@ -926,7 +1372,7 @@ export class DbSecretsVaultService implements ISecretsVaultService {
926
1372
  | { outcome: 'refreshed'; accessToken: string; wiped: false }
927
1373
  | { outcome: 'rejected'; accessToken: null; wiped: boolean }
928
1374
  > {
929
- const next: OAuthBlob = { clientSecret: blob.clientSecret };
1375
+ const next: OAuthBlob = { clientSecret: blob.clientSecret, ...this.inFlightOf(blob) };
930
1376
  // Guarded on the ciphertext we read, so a concurrent refresh that
931
1377
  // already persisted ROTATED tokens can't be wiped by our stale
932
1378
  // failure. When that guard trips (0 rows), the failure was against a
@@ -964,17 +1410,21 @@ export class DbSecretsVaultService implements ISecretsVaultService {
964
1410
  const blob: OAuthBlob = { clientSecret: provider.clientSecret };
965
1411
  const valueEncrypted = this.crypto().encrypt(JSON.stringify(blob));
966
1412
  const oauthMeta = this.publicProviderMeta(provider);
967
- await this.db
968
- .insert(secrets)
969
- .values({ userId: null, key, kind: 'oauth', label, valueEncrypted, oauthMeta })
970
- // Preserve an existing row's label on re-upsert (matches
971
- // `putSharedOAuthClientSecret`): the optional input label is only for the
972
- // FIRST write, so a later re-registration can't null out an owner label.
973
- .onConflictDoUpdate({
974
- target: secrets.key,
975
- targetWhere: isNull(secrets.userId),
976
- set: { kind: 'oauth', valueEncrypted, oauthMeta, updatedAt: new Date() },
977
- });
1413
+ await this.revokedReplacement(and(isNull(secrets.userId), eq(secrets.key, key)), (guard) =>
1414
+ this.db
1415
+ .insert(secrets)
1416
+ .values({ userId: null, key, kind: 'oauth', label, valueEncrypted, oauthMeta })
1417
+ // Preserve an existing row's label on re-upsert (matches
1418
+ // `putSharedOAuthClientSecret`): the optional input label is only for the
1419
+ // FIRST write, so a later re-registration can't null out an owner label.
1420
+ .onConflictDoUpdate({
1421
+ target: secrets.key,
1422
+ targetWhere: isNull(secrets.userId),
1423
+ set: { kind: 'oauth', valueEncrypted, oauthMeta, updatedAt: new Date() },
1424
+ ...(guard ? { setWhere: guard } : {}),
1425
+ })
1426
+ .returning({ id: secrets.id }),
1427
+ );
978
1428
  this.notifyMutation(null);
979
1429
  }
980
1430
 
@@ -1066,18 +1516,22 @@ export class DbSecretsVaultService implements ISecretsVaultService {
1066
1516
  }
1067
1517
 
1068
1518
  private readMeta(raw: unknown): Required<Pick<OAuthProviderConfig, 'authorizationUrl' | 'tokenUrl' | 'clientId'>> &
1069
- Pick<OAuthProviderConfig, 'scopes' | 'authParams' | 'pkce' | 'publicClient' | 'resource'> {
1519
+ Pick<OAuthProviderConfig, 'scopes' | 'authParams' | 'pkce' | 'publicClient' | 'resource' | 'managed'> {
1070
1520
  const meta = (raw ?? {}) as Record<string, unknown>;
1071
1521
  const authorizationUrl = typeof meta.authorizationUrl === 'string' ? meta.authorizationUrl : '';
1072
1522
  const tokenUrl = typeof meta.tokenUrl === 'string' ? meta.tokenUrl : '';
1073
1523
  const clientId = typeof meta.clientId === 'string' ? meta.clientId : '';
1074
- if (!authorizationUrl || !tokenUrl || !clientId) {
1524
+ const managed = this.managedRefOf(meta) ?? undefined;
1525
+ // A managed row has no client id of its own (the broker's app is the
1526
+ // client); everything else must name one, or no flow can run from it.
1527
+ if (!authorizationUrl || !tokenUrl || (!clientId && !managed)) {
1075
1528
  throw new SecretOAuthError('OAuth secret is missing provider configuration');
1076
1529
  }
1077
1530
  return {
1078
1531
  authorizationUrl,
1079
1532
  tokenUrl,
1080
1533
  clientId,
1534
+ managed,
1081
1535
  scopes: Array.isArray(meta.scopes) ? meta.scopes.map(String) : undefined,
1082
1536
  authParams:
1083
1537
  meta.authParams && typeof meta.authParams === 'object'
@@ -1099,16 +1553,27 @@ export class DbSecretsVaultService implements ISecretsVaultService {
1099
1553
  ...(p.pkce ? { pkce: true } : {}),
1100
1554
  ...(p.publicClient ? { publicClient: true } : {}),
1101
1555
  ...(p.resource ? { resource: p.resource } : {}),
1556
+ ...(p.managed ? { managed: { connectorId: p.managed.connectorId, scopeSet: p.managed.scopeSet } } : {}),
1557
+ ...(p.account ? { account: p.account } : {}),
1102
1558
  };
1103
1559
  }
1104
1560
 
1105
- private requireProvider(p: unknown): OAuthProviderConfig {
1561
+ /**
1562
+ * @param opts.managed Accept a MANAGED provider (no client id of its own,
1563
+ * a connector reference instead). Only the managed sign-in paths pass it:
1564
+ * everywhere else a provider needs a client id, and a `managed` field a
1565
+ * caller slipped in is dropped rather than making the row one.
1566
+ */
1567
+ private requireProvider(p: unknown, opts: { managed?: boolean } = {}): OAuthProviderConfig {
1106
1568
  if (!p || typeof p !== 'object') throw new InvalidSecretError('provider is required');
1107
1569
  const cfg = p as Record<string, unknown>;
1108
1570
  const authorizationUrl = this.requireUrl(cfg.authorizationUrl, 'authorizationUrl');
1109
1571
  const tokenUrl = this.requireUrl(cfg.tokenUrl, 'tokenUrl');
1110
- const clientId = typeof cfg.clientId === 'string' ? cfg.clientId.trim() : '';
1111
- if (!clientId) throw new InvalidSecretError('clientId is required');
1572
+ const managed = opts.managed ? (this.managedRefOf(cfg) ?? undefined) : undefined;
1573
+ // A managed sign-in has no client of its own: whatever id a caller sends
1574
+ // beside the connector reference is not stored, so no row can claim one.
1575
+ const clientId = managed ? '' : typeof cfg.clientId === 'string' ? cfg.clientId.trim() : '';
1576
+ if (!clientId && !managed) throw new InvalidSecretError('clientId is required');
1112
1577
  const scopes = Array.isArray(cfg.scopes) ? cfg.scopes.map(String) : undefined;
1113
1578
  const authParams =
1114
1579
  cfg.authParams && typeof cfg.authParams === 'object'
@@ -1129,6 +1594,8 @@ export class DbSecretsVaultService implements ISecretsVaultService {
1129
1594
  typeof cfg.resource === 'string' && cfg.resource.trim()
1130
1595
  ? this.requireUrl(cfg.resource, 'resource')
1131
1596
  : undefined,
1597
+ managed,
1598
+ account: typeof cfg.account === 'string' && cfg.account.trim() ? cfg.account.trim().slice(0, MAX_LABEL_LEN) : undefined,
1132
1599
  };
1133
1600
  }
1134
1601