@bevel-software/platform-core-backend 0.34.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 (908) hide show
  1. package/agent-guide/access-control.md +146 -300
  2. package/agent-guide/conventions.md +23 -25
  3. package/agent-guide/directory-structure.md +72 -120
  4. package/agent-guide/drafts-and-change-requests.md +92 -0
  5. package/agent-guide/for-git-users.md +47 -0
  6. package/agent-guide/glossary.md +36 -0
  7. package/agent-guide/how-agents-find-things.md +49 -0
  8. package/agent-guide/html-views.md +16 -11
  9. package/agent-guide/new-knowledge-base.md +9 -8
  10. package/agent-guide/onboarding.md +111 -0
  11. package/agent-guide/skills.md +67 -42
  12. package/agent-guide/start-here.md +66 -0
  13. package/agent-guide/tool-manuals.md +133 -154
  14. package/agent-guide/tools.md +67 -0
  15. package/agent-guide/where-a-new-file-goes.md +45 -28
  16. package/dist/core/catalog-cache-invalidation.d.ts +2 -2
  17. package/dist/core/catalog-cache-invalidation.d.ts.map +1 -1
  18. package/dist/core/compose-embed.d.ts +2 -2
  19. package/dist/core/compose-embed.d.ts.map +1 -1
  20. package/dist/core/compose-embed.js +1 -1
  21. package/dist/core/compose-embed.js.map +1 -1
  22. package/dist/core/core-ports.d.ts +19 -4
  23. package/dist/core/core-ports.d.ts.map +1 -1
  24. package/dist/core/core-ports.js.map +1 -1
  25. package/dist/core/create-core-server.d.ts.map +1 -1
  26. package/dist/core/create-core-server.js +27 -25
  27. package/dist/core/create-core-server.js.map +1 -1
  28. package/dist/core/create-core-services.d.ts +25 -13
  29. package/dist/core/create-core-services.d.ts.map +1 -1
  30. package/dist/core/create-core-services.js +75 -60
  31. package/dist/core/create-core-services.js.map +1 -1
  32. package/dist/core/lifecycle.d.ts +4 -4
  33. package/dist/core/lifecycle.d.ts.map +1 -1
  34. package/dist/core/lifecycle.js +5 -5
  35. package/dist/core/lifecycle.js.map +1 -1
  36. package/dist/core-config.d.ts +2 -2
  37. package/dist/core-config.js +3 -3
  38. package/dist/core-config.js.map +1 -1
  39. package/dist/index.d.ts +4 -4
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +3 -3
  42. package/dist/index.js.map +1 -1
  43. package/dist/modules/access/access-control.interface.d.ts +22 -0
  44. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  45. package/dist/modules/access/access-control.service.d.ts +3 -1
  46. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  47. package/dist/modules/access/access-control.service.js +76 -1
  48. package/dist/modules/access/access-control.service.js.map +1 -1
  49. package/dist/modules/access/access-declarations.d.ts +2 -2
  50. package/dist/modules/access/access-declarations.d.ts.map +1 -1
  51. package/dist/modules/access/access-declarations.js +2 -2
  52. package/dist/modules/access/access-declarations.js.map +1 -1
  53. package/dist/modules/access/access-mutation.service.d.ts +2 -2
  54. package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
  55. package/dist/modules/access/access-mutation.service.js +2 -2
  56. package/dist/modules/access/access-mutation.service.js.map +1 -1
  57. package/dist/modules/access/access-requests.routes.d.ts +2 -2
  58. package/dist/modules/access/access-requests.routes.d.ts.map +1 -1
  59. package/dist/modules/access/access-requests.routes.js +5 -5
  60. package/dist/modules/access/access-requests.routes.js.map +1 -1
  61. package/dist/modules/access/access-requests.service.d.ts +2 -2
  62. package/dist/modules/access/access-requests.service.d.ts.map +1 -1
  63. package/dist/modules/access/access-requests.service.js +11 -11
  64. package/dist/modules/access/access-requests.service.js.map +1 -1
  65. package/dist/modules/access/access.routes.d.ts +2 -2
  66. package/dist/modules/access/access.routes.d.ts.map +1 -1
  67. package/dist/modules/access/access.routes.js +32 -13
  68. package/dist/modules/access/access.routes.js.map +1 -1
  69. package/dist/modules/access/admin-locked-commit.d.ts +5 -5
  70. package/dist/modules/access/admin-locked-commit.d.ts.map +1 -1
  71. package/dist/modules/access/admin-locked-commit.js +7 -7
  72. package/dist/modules/access/admin-locked-commit.js.map +1 -1
  73. package/dist/modules/access/admin-route-helpers.d.ts +1 -1
  74. package/dist/modules/access/admin-route-helpers.js +5 -5
  75. package/dist/modules/access/admin-route-helpers.js.map +1 -1
  76. package/dist/modules/access/change-read-gate.d.ts +1 -1
  77. package/dist/modules/access/change-read-gate.js +1 -1
  78. package/dist/modules/access/groups-admin.service.d.ts +7 -7
  79. package/dist/modules/access/groups-admin.service.d.ts.map +1 -1
  80. package/dist/modules/access/groups-admin.service.js +6 -6
  81. package/dist/modules/access/groups-admin.service.js.map +1 -1
  82. package/dist/modules/access/index.d.ts +1 -1
  83. package/dist/modules/access/index.d.ts.map +1 -1
  84. package/dist/modules/access/reference-scan.d.ts +1 -1
  85. package/dist/modules/access/roles-admin.service.d.ts +7 -7
  86. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  87. package/dist/modules/access/roles-admin.service.js +8 -8
  88. package/dist/modules/access/roles-admin.service.js.map +1 -1
  89. package/dist/modules/access/synced-groups-committer.d.ts +4 -4
  90. package/dist/modules/access/synced-groups-committer.d.ts.map +1 -1
  91. package/dist/modules/access/synced-groups-committer.js +7 -7
  92. package/dist/modules/access/synced-groups-committer.js.map +1 -1
  93. package/dist/modules/access/user-access-removal.service.d.ts +6 -6
  94. package/dist/modules/access/user-access-removal.service.d.ts.map +1 -1
  95. package/dist/modules/access/user-access-removal.service.js +4 -4
  96. package/dist/modules/access/user-access-removal.service.js.map +1 -1
  97. package/dist/modules/access-model/access-errors.d.ts +4 -4
  98. package/dist/modules/access-model/access-errors.d.ts.map +1 -1
  99. package/dist/modules/access-model/access-errors.js +4 -4
  100. package/dist/modules/access-model/access-errors.js.map +1 -1
  101. package/dist/modules/access-model/access-grammar.d.ts +1 -1
  102. package/dist/modules/access-model/access-grammar.js +4 -4
  103. package/dist/modules/access-model/change-gate.d.ts +1 -1
  104. package/dist/modules/access-model/change-gate.js +1 -1
  105. package/dist/modules/access-model/roles-yaml-guard.d.ts +4 -4
  106. package/dist/modules/access-model/roles-yaml-guard.d.ts.map +1 -1
  107. package/dist/modules/access-model/roles-yaml-guard.js +4 -4
  108. package/dist/modules/access-model/roles-yaml-guard.js.map +1 -1
  109. package/dist/modules/agent-guide/agent-guide.d.ts +63 -18
  110. package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
  111. package/dist/modules/agent-guide/agent-guide.js +144 -30
  112. package/dist/modules/agent-guide/agent-guide.js.map +1 -1
  113. package/dist/modules/agent-guide/agent-guide.tools.d.ts +6 -2
  114. package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -1
  115. package/dist/modules/agent-guide/agent-guide.tools.js +45 -31
  116. package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -1
  117. package/dist/modules/agent-guide/guide-budget.d.ts +51 -0
  118. package/dist/modules/agent-guide/guide-budget.d.ts.map +1 -0
  119. package/dist/modules/agent-guide/guide-budget.js +71 -0
  120. package/dist/modules/agent-guide/guide-budget.js.map +1 -0
  121. package/dist/modules/agent-guide/index.d.ts +2 -1
  122. package/dist/modules/agent-guide/index.d.ts.map +1 -1
  123. package/dist/modules/agent-guide/index.js +2 -1
  124. package/dist/modules/agent-guide/index.js.map +1 -1
  125. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +12 -5
  126. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -1
  127. package/dist/modules/agent-instructions/agent-instructions.routes.js +13 -6
  128. package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -1
  129. package/dist/modules/agent-instructions/compose.d.ts +15 -8
  130. package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
  131. package/dist/modules/agent-instructions/compose.js +28 -10
  132. package/dist/modules/agent-instructions/compose.js.map +1 -1
  133. package/dist/modules/agent-instructions/shared-file-rules.d.ts +14 -6
  134. package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
  135. package/dist/modules/agent-instructions/shared-file-rules.js +60 -18
  136. package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
  137. package/dist/modules/auth/account-erasure.service.d.ts +36 -7
  138. package/dist/modules/auth/account-erasure.service.d.ts.map +1 -1
  139. package/dist/modules/auth/account-erasure.service.js +30 -10
  140. package/dist/modules/auth/account-erasure.service.js.map +1 -1
  141. package/dist/modules/auth/account-hooks.d.ts +20 -0
  142. package/dist/modules/auth/account-hooks.d.ts.map +1 -1
  143. package/dist/modules/auth/account-hooks.js +10 -0
  144. package/dist/modules/auth/account-hooks.js.map +1 -1
  145. package/dist/modules/auth/account-switch-lock.js +2 -2
  146. package/dist/modules/auth/account-switch-lock.js.map +1 -1
  147. package/dist/modules/auth/account.routes.d.ts +7 -2
  148. package/dist/modules/auth/account.routes.d.ts.map +1 -1
  149. package/dist/modules/auth/account.routes.js +23 -4
  150. package/dist/modules/auth/account.routes.js.map +1 -1
  151. package/dist/modules/auth/auth.service.d.ts +1 -1
  152. package/dist/modules/auth/auth.service.js +4 -4
  153. package/dist/modules/auth/auth.service.js.map +1 -1
  154. package/dist/modules/auth/index.d.ts +1 -0
  155. package/dist/modules/auth/index.d.ts.map +1 -1
  156. package/dist/modules/code-mode/code-mode.tool.js +2 -2
  157. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  158. package/dist/modules/connection-probe/connection-probe.service.d.ts +0 -12
  159. package/dist/modules/connection-probe/connection-probe.service.d.ts.map +1 -1
  160. package/dist/modules/connection-probe/connection-probe.service.js +1 -38
  161. package/dist/modules/connection-probe/connection-probe.service.js.map +1 -1
  162. package/dist/modules/database/core-schema.js +2 -2
  163. package/dist/modules/database/core-schema.js.map +1 -1
  164. package/dist/modules/diff/diff.routes.d.ts +3 -3
  165. package/dist/modules/diff/diff.routes.d.ts.map +1 -1
  166. package/dist/modules/diff/diff.routes.js +7 -7
  167. package/dist/modules/diff/diff.routes.js.map +1 -1
  168. package/dist/modules/embed/embed.routes.js +1 -1
  169. package/dist/modules/embed/embed.routes.js.map +1 -1
  170. package/dist/modules/embed/embed.service.d.ts +4 -4
  171. package/dist/modules/embed/embed.service.d.ts.map +1 -1
  172. package/dist/modules/embed/embed.service.js +18 -18
  173. package/dist/modules/embed/embed.service.js.map +1 -1
  174. package/dist/modules/kb-fs/branch-name.js +8 -8
  175. package/dist/modules/kb-fs/branch-name.js.map +1 -1
  176. package/dist/modules/kb-fs/file-change-notifier.d.ts +2 -2
  177. package/dist/modules/kb-fs/locking-filesystem.d.ts +9 -9
  178. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
  179. package/dist/modules/kb-fs/locking-filesystem.js +27 -27
  180. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
  181. package/dist/modules/kb-fs/repo-path.js +5 -5
  182. package/dist/modules/kb-fs/repo-path.js.map +1 -1
  183. package/dist/modules/kb-fs/tmp-ref.d.ts +2 -2
  184. package/dist/modules/kb-fs/tmp-ref.d.ts.map +1 -1
  185. package/dist/modules/kb-fs/tmp-ref.js +2 -2
  186. package/dist/modules/kb-fs/tmp-ref.js.map +1 -1
  187. package/dist/modules/kb-sync/index.d.ts +1 -1
  188. package/dist/modules/kb-sync/index.d.ts.map +1 -1
  189. package/dist/modules/kb-sync/kb-sync.interface.d.ts +3 -3
  190. package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -1
  191. package/dist/modules/kb-sync/kb-sync.service.d.ts +3 -3
  192. package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
  193. package/dist/modules/kb-sync/kb-sync.service.js +10 -10
  194. package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
  195. package/dist/modules/mcp/mcp.service.d.ts +11 -4
  196. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  197. package/dist/modules/mcp/mcp.service.js +80 -53
  198. package/dist/modules/mcp/mcp.service.js.map +1 -1
  199. package/dist/modules/onboarding/onboarding.contract.d.ts +1 -1
  200. package/dist/modules/onboarding/onboarding.routes.js +2 -2
  201. package/dist/modules/onboarding/onboarding.routes.js.map +1 -1
  202. package/dist/modules/onboarding/starter-pack.service.d.ts +4 -4
  203. package/dist/modules/onboarding/starter-pack.service.d.ts.map +1 -1
  204. package/dist/modules/onboarding/starter-pack.service.js +4 -4
  205. package/dist/modules/onboarding/starter-pack.service.js.map +1 -1
  206. package/dist/modules/plugins/join-request-jobs.service.d.ts +2 -2
  207. package/dist/modules/plugins/join-request-jobs.service.d.ts.map +1 -1
  208. package/dist/modules/plugins/join-request-jobs.service.js +7 -7
  209. package/dist/modules/plugins/join-request-jobs.service.js.map +1 -1
  210. package/dist/modules/plugins/join-requests.service.d.ts +3 -3
  211. package/dist/modules/plugins/join-requests.service.d.ts.map +1 -1
  212. package/dist/modules/plugins/join-requests.service.js +5 -5
  213. package/dist/modules/plugins/join-requests.service.js.map +1 -1
  214. package/dist/modules/plugins/plugin-rename.service.js +1 -1
  215. package/dist/modules/plugins/plugin-rename.service.js.map +1 -1
  216. package/dist/modules/plugins/plugins.routes.d.ts +2 -2
  217. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  218. package/dist/modules/plugins/plugins.routes.js +8 -8
  219. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  220. package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +87 -2
  221. package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
  222. package/dist/modules/secrets-vault/db-secrets-vault.service.js +635 -74
  223. package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
  224. package/dist/modules/secrets-vault/index.d.ts +3 -2
  225. package/dist/modules/secrets-vault/index.d.ts.map +1 -1
  226. package/dist/modules/secrets-vault/index.js +2 -1
  227. package/dist/modules/secrets-vault/index.js.map +1 -1
  228. package/dist/modules/secrets-vault/managed-sign-in-erasure.d.ts +36 -0
  229. package/dist/modules/secrets-vault/managed-sign-in-erasure.d.ts.map +1 -0
  230. package/dist/modules/secrets-vault/managed-sign-in-erasure.js +37 -0
  231. package/dist/modules/secrets-vault/managed-sign-in-erasure.js.map +1 -0
  232. package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +205 -11
  233. package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
  234. package/dist/modules/secrets-vault/secrets-vault.contract.js +27 -0
  235. package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
  236. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +7 -1
  237. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
  238. package/dist/modules/secrets-vault/secrets-vault.routes.js +206 -45
  239. package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
  240. package/dist/modules/settings/connection-check.js +1 -1
  241. package/dist/modules/settings/connection-check.js.map +1 -1
  242. package/dist/modules/settings/deployment-settings.service.js +1 -1
  243. package/dist/modules/settings/git-root-folders.js +1 -1
  244. package/dist/modules/settings/git-root-folders.js.map +1 -1
  245. package/dist/modules/settings/oidc-check.d.ts.map +1 -1
  246. package/dist/modules/settings/oidc-check.js +5 -17
  247. package/dist/modules/settings/oidc-check.js.map +1 -1
  248. package/dist/modules/settings/setup.routes.d.ts +1 -1
  249. package/dist/modules/settings/setup.routes.js +1 -1
  250. package/dist/modules/skills/pending-skills.service.d.ts +3 -3
  251. package/dist/modules/skills/pending-skills.service.d.ts.map +1 -1
  252. package/dist/modules/skills/pending-skills.service.js +4 -4
  253. package/dist/modules/skills/pending-skills.service.js.map +1 -1
  254. package/dist/modules/skills/skill-access-requests.routes.d.ts +2 -2
  255. package/dist/modules/skills/skill-access-requests.routes.d.ts.map +1 -1
  256. package/dist/modules/skills/skill-access-requests.routes.js +5 -5
  257. package/dist/modules/skills/skill-access-requests.routes.js.map +1 -1
  258. package/dist/modules/skills/skills.tools.js +1 -1
  259. package/dist/modules/skills/skills.tools.js.map +1 -1
  260. package/dist/modules/tool-helpers/tool-context.d.ts +4 -4
  261. package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
  262. package/dist/modules/tool-helpers/tool-context.js +2 -2
  263. package/dist/modules/tool-helpers/tool-context.js.map +1 -1
  264. package/dist/modules/tool-helpers/tool-def.js +1 -1
  265. package/dist/modules/tool-helpers/tool-def.js.map +1 -1
  266. package/dist/modules/tool-helpers/tool-handler.js +2 -2
  267. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  268. package/dist/modules/tool-helpers/tool.contract.d.ts +4 -4
  269. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  270. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts +7 -1
  271. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
  272. package/dist/modules/tool-manuals/mcp-json-discovery.js +69 -38
  273. package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
  274. package/dist/modules/tool-manuals/mcp-server-edit.service.js +1 -1
  275. package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -1
  276. package/dist/modules/tool-manuals/pending-tools.service.d.ts +3 -3
  277. package/dist/modules/tool-manuals/pending-tools.service.d.ts.map +1 -1
  278. package/dist/modules/tool-manuals/pending-tools.service.js +4 -4
  279. package/dist/modules/tool-manuals/pending-tools.service.js.map +1 -1
  280. package/dist/modules/tool-manuals/tool-delete.service.js +1 -1
  281. package/dist/modules/tool-manuals/tool-delete.service.js.map +1 -1
  282. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +70 -3
  283. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  284. package/dist/modules/tool-manuals/tool-manuals.service.d.ts +18 -8
  285. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  286. package/dist/modules/tool-manuals/tool-manuals.service.js +166 -49
  287. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  288. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +11 -2
  289. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  290. package/dist/modules/tool-manuals/tool-manuals.tools.js +66 -24
  291. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  292. package/dist/modules/version-control/agent-tools/change-request-read-shape.d.ts.map +1 -0
  293. package/dist/modules/version-control/agent-tools/change-request-read-shape.js.map +1 -0
  294. package/dist/modules/version-control/agent-tools/change-request-read.tools.d.ts.map +1 -0
  295. package/dist/modules/{workflow → version-control}/agent-tools/change-request-read.tools.js +2 -2
  296. package/dist/modules/version-control/agent-tools/change-request-read.tools.js.map +1 -0
  297. package/dist/modules/version-control/agent-tools/change-request-summary.d.ts.map +1 -0
  298. package/dist/modules/version-control/agent-tools/change-request-summary.js.map +1 -0
  299. package/dist/modules/{workflow/agent-tools/workflow.tools.d.ts → version-control/agent-tools/version-control.tools.d.ts} +4 -4
  300. package/dist/modules/version-control/agent-tools/version-control.tools.d.ts.map +1 -0
  301. package/dist/modules/{workflow/agent-tools/workflow.tools.js → version-control/agent-tools/version-control.tools.js} +53 -49
  302. package/dist/modules/version-control/agent-tools/version-control.tools.js.map +1 -0
  303. package/dist/modules/{workflow → version-control}/change-request-hooks.d.ts +1 -1
  304. package/dist/modules/version-control/change-request-hooks.d.ts.map +1 -0
  305. package/dist/modules/version-control/change-request-hooks.js.map +1 -0
  306. package/dist/modules/version-control/change-request-review/approval-lock.d.ts.map +1 -0
  307. package/dist/modules/version-control/change-request-review/approval-lock.js.map +1 -0
  308. package/dist/modules/{workflow/review-workflow/review-workflow.interface.d.ts → version-control/change-request-review/change-request-review.interface.d.ts} +2 -2
  309. package/dist/modules/version-control/change-request-review/change-request-review.interface.d.ts.map +1 -0
  310. package/dist/modules/version-control/change-request-review/change-request-review.interface.js +2 -0
  311. package/dist/modules/version-control/change-request-review/change-request-review.interface.js.map +1 -0
  312. package/dist/modules/{workflow/review-workflow/review-workflow.service.d.ts → version-control/change-request-review/change-request-review.service.d.ts} +3 -3
  313. package/dist/modules/version-control/change-request-review/change-request-review.service.d.ts.map +1 -0
  314. package/dist/modules/{workflow/review-workflow/review-workflow.service.js → version-control/change-request-review/change-request-review.service.js} +26 -26
  315. package/dist/modules/version-control/change-request-review/change-request-review.service.js.map +1 -0
  316. package/dist/modules/{workflow → version-control}/event-bus.d.ts +8 -8
  317. package/dist/modules/version-control/event-bus.d.ts.map +1 -0
  318. package/dist/modules/{workflow → version-control}/event-bus.js +4 -4
  319. package/dist/modules/version-control/event-bus.js.map +1 -0
  320. package/dist/modules/{workflow → version-control}/events-delivery.d.ts +4 -4
  321. package/dist/modules/version-control/events-delivery.d.ts.map +1 -0
  322. package/dist/modules/version-control/events-delivery.js.map +1 -0
  323. package/dist/modules/{workflow → version-control}/events.routes.d.ts +4 -4
  324. package/dist/modules/version-control/events.routes.d.ts.map +1 -0
  325. package/dist/modules/version-control/events.routes.js.map +1 -0
  326. package/dist/modules/{workflow → version-control}/file-lock.service.d.ts +2 -2
  327. package/dist/modules/version-control/file-lock.service.d.ts.map +1 -0
  328. package/dist/modules/{workflow → version-control}/file-lock.service.js +6 -6
  329. package/dist/modules/version-control/file-lock.service.js.map +1 -0
  330. package/dist/modules/version-control/git/change-request-link.d.ts.map +1 -0
  331. package/dist/modules/version-control/git/change-request-link.js.map +1 -0
  332. package/dist/modules/version-control/git/git-version.d.ts.map +1 -0
  333. package/dist/modules/version-control/git/git-version.js.map +1 -0
  334. package/dist/modules/{workflow → version-control}/git/git.service.d.ts +8 -8
  335. package/dist/modules/version-control/git/git.service.d.ts.map +1 -0
  336. package/dist/modules/{workflow → version-control}/git/git.service.js +53 -53
  337. package/dist/modules/version-control/git/git.service.js.map +1 -0
  338. package/dist/modules/version-control/git/merge-commit.d.ts.map +1 -0
  339. package/dist/modules/version-control/git/merge-commit.js.map +1 -0
  340. package/dist/modules/version-control/git/node-git-runner.d.ts.map +1 -0
  341. package/dist/modules/{workflow → version-control}/git/node-git-runner.js +1 -1
  342. package/dist/modules/version-control/git/node-git-runner.js.map +1 -0
  343. package/dist/modules/{workflow → version-control}/git/pull-request.service.d.ts +5 -5
  344. package/dist/modules/version-control/git/pull-request.service.d.ts.map +1 -0
  345. package/dist/modules/{workflow → version-control}/git/pull-request.service.js +10 -10
  346. package/dist/modules/version-control/git/pull-request.service.js.map +1 -0
  347. package/dist/modules/{workflow → version-control}/index.d.ts +6 -5
  348. package/dist/modules/version-control/index.d.ts.map +1 -0
  349. package/dist/modules/{workflow → version-control}/index.js +3 -2
  350. package/dist/modules/version-control/index.js.map +1 -0
  351. package/dist/modules/{workflow → version-control}/pending-commits.service.d.ts +2 -2
  352. package/dist/modules/version-control/pending-commits.service.d.ts.map +1 -0
  353. package/dist/modules/{workflow → version-control}/pending-commits.service.js +2 -2
  354. package/dist/modules/version-control/pending-commits.service.js.map +1 -0
  355. package/dist/modules/{workflow → version-control}/pending-commits.worker.d.ts +5 -5
  356. package/dist/modules/version-control/pending-commits.worker.d.ts.map +1 -0
  357. package/dist/modules/{workflow → version-control}/pending-commits.worker.js +3 -3
  358. package/dist/modules/version-control/pending-commits.worker.js.map +1 -0
  359. package/dist/modules/{workflow → version-control}/read-on-branch.d.ts +2 -2
  360. package/dist/modules/version-control/read-on-branch.d.ts.map +1 -0
  361. package/dist/modules/{workflow → version-control}/read-on-branch.js +2 -2
  362. package/dist/modules/version-control/read-on-branch.js.map +1 -0
  363. package/dist/modules/version-control/recovery-bot.d.ts.map +1 -0
  364. package/dist/modules/version-control/recovery-bot.js.map +1 -0
  365. package/dist/modules/version-control/sanitize-error.d.ts.map +1 -0
  366. package/dist/modules/version-control/sanitize-error.js.map +1 -0
  367. package/dist/modules/{workflow/workflow-hooks.d.ts → version-control/version-control-hooks.d.ts} +4 -4
  368. package/dist/modules/version-control/version-control-hooks.d.ts.map +1 -0
  369. package/dist/modules/{workflow/workflow-hooks.js → version-control/version-control-hooks.js} +3 -3
  370. package/dist/modules/version-control/version-control-hooks.js.map +1 -0
  371. package/dist/modules/{workflow/workflow.routes.d.ts → version-control/version-control.routes.d.ts} +6 -5
  372. package/dist/modules/version-control/version-control.routes.d.ts.map +1 -0
  373. package/dist/modules/{workflow/workflow.routes.js → version-control/version-control.routes.js} +61 -60
  374. package/dist/modules/version-control/version-control.routes.js.map +1 -0
  375. package/dist/modules/{workflow/workflow.service.d.ts → version-control/version-control.service.d.ts} +26 -31
  376. package/dist/modules/version-control/version-control.service.d.ts.map +1 -0
  377. package/dist/modules/{workflow/workflow.service.js → version-control/version-control.service.js} +89 -94
  378. package/dist/modules/version-control/version-control.service.js.map +1 -0
  379. package/dist/modules/workspace/agent-access.gate.d.ts +6 -6
  380. package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -1
  381. package/dist/modules/workspace/agent-access.gate.js +2 -2
  382. package/dist/modules/workspace/agent-access.gate.js.map +1 -1
  383. package/dist/modules/workspace/git-internals.middleware.d.ts +1 -1
  384. package/dist/modules/workspace/git-internals.middleware.js +2 -2
  385. package/dist/modules/workspace/kb-repository.js +1 -1
  386. package/dist/modules/workspace/kb-repository.js.map +1 -1
  387. package/dist/modules/workspace/startup/kb-git.d.ts +1 -1
  388. package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -1
  389. package/dist/modules/workspace/startup/kb-git.js +1 -1
  390. package/dist/modules/workspace/startup/kb-git.js.map +1 -1
  391. package/dist/modules/workspace/startup/steps/seed-tree.d.ts +2 -2
  392. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
  393. package/dist/modules/workspace/startup/steps/seed-tree.js +1 -1
  394. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
  395. package/dist/modules/workspace/startup/steps/template-files.step.d.ts +2 -2
  396. package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
  397. package/dist/modules/workspace/startup/steps/template-files.step.js +2 -2
  398. package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
  399. package/dist/modules/workspace/startup/steps/template-source.d.ts +17 -4
  400. package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -1
  401. package/dist/modules/workspace/startup/steps/template-source.js +8 -5
  402. package/dist/modules/workspace/startup/steps/template-source.js.map +1 -1
  403. package/dist/modules/workspace/workspace-tmp.js +3 -3
  404. package/dist/modules/workspace/workspace-tmp.js.map +1 -1
  405. package/dist/modules/workspace/workspace.routes.d.ts +4 -4
  406. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  407. package/dist/modules/workspace/workspace.routes.js +22 -22
  408. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  409. package/dist/modules/workspace/workspace.service.d.ts +6 -6
  410. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  411. package/dist/modules/workspace/workspace.service.js +11 -11
  412. package/dist/modules/workspace/workspace.service.js.map +1 -1
  413. package/dist/modules/workspace/workspace.tools.d.ts +5 -3
  414. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  415. package/dist/modules/workspace/workspace.tools.js +85 -30
  416. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  417. package/dist/shared/canonical-file-identity.d.ts +2 -2
  418. package/dist/shared/canonical-file-identity.js +5 -5
  419. package/dist/shared/canonical-file-identity.js.map +1 -1
  420. package/dist/shared/domain-errors.d.ts +39 -39
  421. package/dist/shared/domain-errors.d.ts.map +1 -1
  422. package/dist/shared/domain-errors.js +44 -44
  423. package/dist/shared/domain-errors.js.map +1 -1
  424. package/dist/shared/git.contract.d.ts +1 -1
  425. package/dist/shared/git.contract.js +1 -1
  426. package/dist/shared/http-errors.d.ts +3 -3
  427. package/dist/shared/http-errors.d.ts.map +1 -1
  428. package/dist/shared/http-errors.js +1 -1
  429. package/dist/shared/http-errors.js.map +1 -1
  430. package/dist/shared/oauth-token-error.d.ts +23 -0
  431. package/dist/shared/oauth-token-error.d.ts.map +1 -0
  432. package/dist/shared/oauth-token-error.js +47 -0
  433. package/dist/shared/oauth-token-error.js.map +1 -0
  434. package/dist/shared/pending-proposals.d.ts +3 -3
  435. package/dist/shared/pending-proposals.d.ts.map +1 -1
  436. package/dist/shared/pending-proposals.js +2 -2
  437. package/dist/shared/pending-proposals.js.map +1 -1
  438. package/dist/shared/read-capped.d.ts +19 -0
  439. package/dist/shared/read-capped.d.ts.map +1 -0
  440. package/dist/shared/read-capped.js +45 -0
  441. package/dist/shared/read-capped.js.map +1 -0
  442. package/dist/tenancy/static-tenant-source.js +1 -1
  443. package/dist/tenancy/static-tenant-source.js.map +1 -1
  444. package/migrations/0012_agent_audit.sql +40 -40
  445. package/migrations/0013_agent_identity_and_key_soft_delete.sql +29 -29
  446. package/migrations/meta/0012_snapshot.json +2246 -2246
  447. package/migrations/meta/0013_snapshot.json +2258 -2258
  448. package/package.json +5 -4
  449. package/src/__tests__/account-admin.ts +27 -0
  450. package/src/__tests__/hexis-catalog.ts +144 -0
  451. package/src/__tests__/kb-context.ts +30 -30
  452. package/src/__tests__/kb-layout-config.test.ts +21 -0
  453. package/src/__tests__/open-change-gate.ts +1 -1
  454. package/src/__tests__/real-disk.ts +21 -0
  455. package/src/__tests__/tokens.ts +19 -0
  456. package/src/__tests__/tool-handler.ts +15 -0
  457. package/src/__tests__/tool-manuals.ts +32 -0
  458. package/src/__tests__/tool-wiring.ts +31 -0
  459. package/src/__tests__/workspace-service.ts +1 -1
  460. package/src/core/__tests__/catalog-cache-invalidation.test.ts +3 -3
  461. package/src/core/__tests__/compose-embed.test.ts +1 -1
  462. package/src/core/__tests__/damaged-document.tools.test.ts +3 -3
  463. package/src/core/__tests__/erasure-slices-wired-once.test.ts +46 -0
  464. package/src/core/__tests__/gated-boot.test.ts +2 -2
  465. package/src/core/__tests__/lifecycle.test.ts +9 -9
  466. package/src/core/__tests__/oauth-sign-in-real-file.test.ts +179 -0
  467. package/src/core/catalog-cache-invalidation.ts +2 -2
  468. package/src/core/compose-embed.ts +3 -3
  469. package/src/core/core-ports.ts +19 -4
  470. package/src/core/create-core-server.ts +40 -26
  471. package/src/core/create-core-services.ts +92 -66
  472. package/src/core/lifecycle.ts +9 -9
  473. package/src/core-config.ts +3 -3
  474. package/src/index.ts +5 -5
  475. package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +4 -4
  476. package/src/modules/access/__tests__/access-own-read-grant.test.ts +14 -14
  477. package/src/modules/access/__tests__/access-requests.routes.test.ts +20 -20
  478. package/src/modules/access/__tests__/access.routes.admins.test.ts +4 -4
  479. package/src/modules/access/__tests__/access.routes.batch.test.ts +4 -4
  480. package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +6 -6
  481. package/src/modules/access/__tests__/access.routes.group-grant.test.ts +6 -6
  482. package/src/modules/access/__tests__/access.routes.overrides.test.ts +5 -5
  483. package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +6 -6
  484. package/src/modules/access/__tests__/access.routes.prospective.test.ts +54 -14
  485. package/src/modules/access/__tests__/access.routes.revoke.test.ts +6 -6
  486. package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +6 -6
  487. package/src/modules/access/__tests__/admin-locked-commit.test.ts +6 -6
  488. package/src/modules/access/__tests__/admin-route-helpers.test.ts +5 -5
  489. package/src/modules/access/__tests__/groups-admin.service.test.ts +27 -27
  490. package/src/modules/access/__tests__/holder-changes.test.ts +127 -0
  491. package/src/modules/access/__tests__/roles-admin.security.test.ts +14 -13
  492. package/src/modules/access/__tests__/roles-admin.service.test.ts +27 -27
  493. package/src/modules/access/__tests__/roles-capabilities.test.ts +19 -19
  494. package/src/modules/access/__tests__/roles.routes.test.ts +6 -6
  495. package/src/modules/access/__tests__/synced-groups-committer.test.ts +13 -13
  496. package/src/modules/access/__tests__/user-access-removal.service.test.ts +29 -29
  497. package/src/modules/access/access-control.interface.ts +26 -0
  498. package/src/modules/access/access-control.service.ts +93 -1
  499. package/src/modules/access/access-declarations.ts +2 -2
  500. package/src/modules/access/access-mutation.service.ts +2 -2
  501. package/src/modules/access/access-requests.routes.ts +7 -7
  502. package/src/modules/access/access-requests.service.ts +13 -13
  503. package/src/modules/access/access.routes.ts +34 -15
  504. package/src/modules/access/admin-locked-commit.ts +11 -11
  505. package/src/modules/access/admin-route-helpers.ts +5 -5
  506. package/src/modules/access/change-read-gate.ts +1 -1
  507. package/src/modules/access/groups-admin.service.ts +7 -7
  508. package/src/modules/access/index.ts +1 -1
  509. package/src/modules/access/reference-scan.ts +1 -1
  510. package/src/modules/access/roles-admin.service.ts +9 -9
  511. package/src/modules/access/synced-groups-committer.ts +11 -11
  512. package/src/modules/access/user-access-removal.service.ts +7 -7
  513. package/src/modules/access-model/__tests__/access-grammar.test.ts +2 -2
  514. package/src/modules/access-model/access-errors.ts +4 -4
  515. package/src/modules/access-model/access-grammar.ts +4 -4
  516. package/src/modules/access-model/change-gate.ts +1 -1
  517. package/src/modules/access-model/roles-yaml-guard.ts +4 -4
  518. package/src/modules/agent-guide/__tests__/agent-guide.test.ts +98 -29
  519. package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +39 -12
  520. package/src/modules/agent-guide/__tests__/guide-budget.test.ts +95 -0
  521. package/src/modules/agent-guide/__tests__/guide-eval.test.ts +182 -0
  522. package/src/modules/agent-guide/__tests__/guide-matches-code.test.ts +263 -0
  523. package/src/modules/agent-guide/__tests__/tool-surface.json +514 -0
  524. package/src/modules/agent-guide/__tests__/tool-surface.test.ts +75 -0
  525. package/src/modules/agent-guide/agent-guide.tools.ts +49 -31
  526. package/src/modules/agent-guide/agent-guide.ts +165 -32
  527. package/src/modules/agent-guide/guide-budget.ts +77 -0
  528. package/src/modules/agent-guide/index.ts +12 -0
  529. package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +4 -5
  530. package/src/modules/agent-instructions/__tests__/compose.test.ts +1 -1
  531. package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +117 -11
  532. package/src/modules/agent-instructions/agent-instructions.routes.ts +12 -5
  533. package/src/modules/agent-instructions/compose.ts +28 -9
  534. package/src/modules/agent-instructions/shared-file-rules.ts +62 -18
  535. package/src/modules/agent-scope/__tests__/agent-surfaces.connect-as.test.ts +1 -1
  536. package/src/modules/audit/__tests__/agent-rest-audit.middleware.test.ts +113 -113
  537. package/src/modules/audit/__tests__/event-classifier.test.ts +121 -121
  538. package/src/modules/audit/event-classifier.ts +155 -155
  539. package/src/modules/auth/__tests__/account-deactivation.test.ts +1 -1
  540. package/src/modules/auth/__tests__/account-erasure.approval-gate.test.ts +4 -4
  541. package/src/modules/auth/__tests__/account-erasure.participants.test.ts +56 -0
  542. package/src/modules/auth/__tests__/account-switch-lock.pg.test.ts +2 -2
  543. package/src/modules/auth/__tests__/account-switch-lock.test.ts +2 -2
  544. package/src/modules/auth/__tests__/account.routes.test.ts +73 -11
  545. package/src/modules/auth/__tests__/auth.service.test.ts +1 -1
  546. package/src/modules/auth/account-erasure.service.ts +27 -9
  547. package/src/modules/auth/account-hooks.ts +34 -0
  548. package/src/modules/auth/account-switch-lock.ts +2 -2
  549. package/src/modules/auth/account.routes.ts +23 -5
  550. package/src/modules/auth/auth.service.ts +4 -4
  551. package/src/modules/auth/index.ts +1 -0
  552. package/src/modules/code-mode/code-mode.tool.ts +2 -2
  553. package/src/modules/connection-probe/__tests__/connection-probe.service.test.ts +5 -24
  554. package/src/modules/connection-probe/connection-probe.service.ts +1 -34
  555. package/src/modules/database/__tests__/advisory-lock.test.ts +169 -169
  556. package/src/modules/database/__tests__/migrations-unqualified.test.ts +32 -32
  557. package/src/modules/database/core-schema.ts +2 -2
  558. package/src/modules/diff/__tests__/diff.routes.read-gate.test.ts +2 -2
  559. package/src/modules/diff/__tests__/diff.routes.rejectPathsLocked.test.ts +12 -12
  560. package/src/modules/diff/diff.routes.ts +8 -8
  561. package/src/modules/embed/__tests__/embed.service.lock.test.ts +7 -7
  562. package/src/modules/embed/__tests__/embed.service.test.ts +42 -42
  563. package/src/modules/embed/__tests__/embed.tools.test.ts +1 -1
  564. package/src/modules/embed/embed.routes.ts +1 -1
  565. package/src/modules/embed/embed.service.ts +17 -17
  566. package/src/modules/kb-fs/__tests__/branch-name.test.ts +2 -2
  567. package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +260 -260
  568. package/src/modules/kb-fs/__tests__/move-with-edits.test.ts +34 -34
  569. package/src/modules/kb-fs/__tests__/repo-path.test.ts +8 -8
  570. package/src/modules/kb-fs/branch-name.ts +8 -8
  571. package/src/modules/kb-fs/file-change-notifier.ts +2 -2
  572. package/src/modules/kb-fs/locking-filesystem.ts +30 -30
  573. package/src/modules/kb-fs/repo-path.ts +5 -5
  574. package/src/modules/kb-fs/tmp-ref.ts +3 -3
  575. package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +36 -36
  576. package/src/modules/kb-sync/index.ts +1 -1
  577. package/src/modules/kb-sync/kb-sync.interface.ts +3 -3
  578. package/src/modules/kb-sync/kb-sync.service.ts +9 -9
  579. package/src/modules/marketplace/__tests__/github-facade.test.ts +1 -1
  580. package/src/modules/marketplace/__tests__/marketplace-git.test.ts +1 -1
  581. package/src/modules/mcp/__tests__/mcp-proxy-helpers.test.ts +4 -2
  582. package/src/modules/mcp/__tests__/mcp.e2e.test.ts +69 -8
  583. package/src/modules/mcp/__tests__/mcp.service.test.ts +91 -13
  584. package/src/modules/mcp/__tests__/read-only-hint.pg.test.ts +194 -0
  585. package/src/modules/mcp/mcp.service.ts +93 -62
  586. package/src/modules/onboarding/__tests__/starter-pack.service.test.ts +55 -55
  587. package/src/modules/onboarding/onboarding.contract.ts +1 -1
  588. package/src/modules/onboarding/onboarding.routes.ts +2 -2
  589. package/src/modules/onboarding/starter-pack.service.ts +6 -6
  590. package/src/modules/plugins/__tests__/bundle-dialect.e2e.test.ts +1 -1
  591. package/src/modules/plugins/__tests__/compile-marketplace.test.ts +1 -1
  592. package/src/modules/plugins/__tests__/join-request-jobs.service.test.ts +43 -43
  593. package/src/modules/plugins/__tests__/join-requests.service.test.ts +19 -19
  594. package/src/modules/plugins/__tests__/join-requests.settlement.test.ts +26 -26
  595. package/src/modules/plugins/__tests__/plugins.routes.test.ts +25 -25
  596. package/src/modules/plugins/join-request-jobs.service.ts +10 -10
  597. package/src/modules/plugins/join-requests.service.ts +4 -4
  598. package/src/modules/plugins/plugin-rename.service.ts +1 -1
  599. package/src/modules/plugins/plugins.routes.ts +9 -9
  600. package/src/modules/secrets-vault/__tests__/db-secrets-vault.managed.test.ts +773 -0
  601. package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +458 -4
  602. package/src/modules/secrets-vault/__tests__/managed-sign-in.route.test.ts +351 -0
  603. package/src/modules/secrets-vault/__tests__/oauth-live-declaration.route.test.ts +218 -0
  604. package/src/modules/secrets-vault/__tests__/oauth-return-to.route.test.ts +39 -6
  605. package/src/modules/secrets-vault/db-secrets-vault.service.ts +728 -118
  606. package/src/modules/secrets-vault/index.ts +8 -1
  607. package/src/modules/secrets-vault/managed-sign-in-erasure.ts +41 -0
  608. package/src/modules/secrets-vault/secrets-variable-loader.ts +134 -134
  609. package/src/modules/secrets-vault/secrets-vault.contract.ts +213 -11
  610. package/src/modules/secrets-vault/secrets-vault.routes.ts +244 -45
  611. package/src/modules/settings/__tests__/repository-source.test.ts +1 -1
  612. package/src/modules/settings/__tests__/setup.routes.layout-phase.test.ts +1 -1
  613. package/src/modules/settings/__tests__/setup.routes.managed-phase.test.ts +1 -1
  614. package/src/modules/settings/connection-check.ts +1 -1
  615. package/src/modules/settings/deployment-settings.service.ts +1 -1
  616. package/src/modules/settings/git-root-folders.ts +1 -1
  617. package/src/modules/settings/oidc-check.ts +5 -18
  618. package/src/modules/settings/setup.routes.ts +1 -1
  619. package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +3 -3
  620. package/src/modules/skills/__tests__/branch-skills.tools.test.ts +1 -1
  621. package/src/modules/skills/__tests__/pending-skills.service.test.ts +8 -8
  622. package/src/modules/skills/__tests__/skill-access-requests.routes.test.ts +18 -18
  623. package/src/modules/skills/__tests__/skills.service.test.ts +3 -3
  624. package/src/modules/skills/pending-skills.service.ts +3 -3
  625. package/src/modules/skills/skill-access-requests.routes.ts +7 -7
  626. package/src/modules/skills/skills.tools.ts +1 -1
  627. package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +22 -14
  628. package/src/modules/tool-helpers/__tests__/branch-declaration.test.ts +3 -3
  629. package/src/modules/tool-helpers/__tests__/can-write.tools.test.ts +3 -3
  630. package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +4 -4
  631. package/src/modules/tool-helpers/__tests__/route-tool-schemas.catalog.test.ts +38 -0
  632. package/src/modules/tool-helpers/__tests__/tools-surface.test.ts +3 -3
  633. package/src/modules/tool-helpers/__tests__/validate-token.test.ts +1 -1
  634. package/src/modules/tool-helpers/tool-context.ts +6 -6
  635. package/src/modules/tool-helpers/tool-def.ts +1 -1
  636. package/src/modules/tool-helpers/tool-handler.ts +2 -2
  637. package/src/modules/tool-helpers/tool.contract.ts +4 -4
  638. package/src/modules/tool-manuals/__tests__/pending-tools.service.test.ts +9 -9
  639. package/src/modules/tool-manuals/__tests__/tool-manuals.managed-sign-in.test.ts +89 -0
  640. package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +197 -11
  641. package/src/modules/tool-manuals/__tests__/tool-manuals.restart.test.ts +1 -1
  642. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +132 -6
  643. package/src/modules/tool-manuals/mcp-json-discovery.ts +73 -34
  644. package/src/modules/tool-manuals/mcp-server-edit.service.ts +1 -1
  645. package/src/modules/tool-manuals/pending-tools.service.ts +3 -3
  646. package/src/modules/tool-manuals/tool-delete.service.ts +1 -1
  647. package/src/modules/tool-manuals/tool-manuals.contract.ts +63 -3
  648. package/src/modules/tool-manuals/tool-manuals.service.ts +191 -51
  649. package/src/modules/tool-manuals/tool-manuals.tools.ts +93 -28
  650. package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +4 -4
  651. package/src/modules/tool-registry/__tests__/read-only-hints.test.ts +3 -3
  652. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +12 -119
  653. package/src/modules/{workflow → version-control}/__tests__/apply-failure.test.ts +54 -54
  654. package/src/modules/{workflow → version-control}/__tests__/event-bus.test.ts +27 -27
  655. package/src/modules/{workflow → version-control}/__tests__/events-delivery.test.ts +37 -37
  656. package/src/modules/{workflow → version-control}/__tests__/file-lock.service.deletion-gate.test.ts +2 -2
  657. package/src/modules/{workflow → version-control}/__tests__/file-lock.service.path-identity.test.ts +8 -8
  658. package/src/modules/{workflow → version-control}/__tests__/merge-announces-tree.test.ts +11 -11
  659. package/src/modules/{workflow → version-control}/__tests__/pending-commits.worker.concurrency.test.ts +9 -9
  660. package/src/modules/{workflow → version-control}/__tests__/pending-commits.worker.test.ts +25 -25
  661. package/src/modules/{workflow → version-control}/__tests__/preserve-roles-yaml.test.ts +42 -42
  662. package/src/modules/version-control/__tests__/public-surface.test.ts +114 -0
  663. package/src/modules/{workflow/__tests__/workflow.routes.folder-proposals.test.ts → version-control/__tests__/version-control.routes.folder-proposals.test.ts} +18 -18
  664. 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
  665. 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
  666. package/src/modules/{workflow/__tests__/workflow.routes.lock-path.test.ts → version-control/__tests__/version-control.routes.lock-path.test.ts} +18 -18
  667. 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
  668. package/src/modules/{workflow/__tests__/workflow.routes.push-refused.test.ts → version-control/__tests__/version-control.routes.push-refused.test.ts} +14 -14
  669. 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
  670. package/src/modules/{workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts → version-control/__tests__/version-control.service.commitFileWhileLocked.test.ts} +14 -14
  671. package/src/modules/{workflow/__tests__/workflow.service.delete-branch.test.ts → version-control/__tests__/version-control.service.delete-branch.test.ts} +14 -14
  672. 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
  673. package/src/modules/{workflow/__tests__/workflow.service.facade.test.ts → version-control/__tests__/version-control.service.facade.test.ts} +97 -99
  674. package/src/modules/{workflow/__tests__/workflow.service.folder-proposals.test.ts → version-control/__tests__/version-control.service.folder-proposals.test.ts} +7 -7
  675. package/src/modules/{workflow/__tests__/workflow.service.platform-restore.test.ts → version-control/__tests__/version-control.service.platform-restore.test.ts} +6 -6
  676. package/src/modules/{workflow/__tests__/workflow.service.read-gate.test.ts → version-control/__tests__/version-control.service.read-gate.test.ts} +9 -9
  677. package/src/modules/{workflow/__tests__/workflow.service.releaseLock.test.ts → version-control/__tests__/version-control.service.releaseLock.test.ts} +42 -42
  678. package/src/modules/{workflow/__tests__/workflow.service.repository-replaced.test.ts → version-control/__tests__/version-control.service.repository-replaced.test.ts} +4 -4
  679. package/src/modules/{workflow/__tests__/workflow.service.sync.test.ts → version-control/__tests__/version-control.service.sync.test.ts} +17 -17
  680. 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
  681. 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
  682. 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
  683. package/src/modules/{workflow → version-control}/agent-tools/__tests__/change-request-read.tools.test.ts +3 -3
  684. package/src/modules/{workflow → version-control}/agent-tools/__tests__/update-change-request.parity.test.ts +16 -16
  685. package/src/modules/{workflow/agent-tools/__tests__/workflow.tools.test.ts → version-control/agent-tools/__tests__/version-control.tools.test.ts} +9 -9
  686. package/src/modules/{workflow → version-control}/agent-tools/change-request-read.tools.ts +2 -2
  687. package/src/modules/{workflow/agent-tools/workflow.tools.ts → version-control/agent-tools/version-control.tools.ts} +52 -48
  688. package/src/modules/{workflow → version-control}/change-request-hooks.ts +1 -1
  689. package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/approval-states.test.ts +5 -5
  690. package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/cancel-pr.test.ts +8 -8
  691. package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/carry-approvals-forward.test.ts +6 -6
  692. package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/erase-approver.test.ts +5 -5
  693. package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/merge-gate.test.ts +3 -3
  694. package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/merge-records-own-commit.test.ts +3 -3
  695. package/src/modules/{workflow/review-workflow/review-workflow.interface.ts → version-control/change-request-review/change-request-review.interface.ts} +1 -1
  696. package/src/modules/{workflow/review-workflow/review-workflow.service.ts → version-control/change-request-review/change-request-review.service.ts} +28 -28
  697. package/src/modules/{workflow → version-control}/event-bus.ts +14 -14
  698. package/src/modules/{workflow → version-control}/events-delivery.ts +11 -11
  699. package/src/modules/{workflow → version-control}/events.routes.ts +5 -5
  700. package/src/modules/{workflow → version-control}/file-lock.service.ts +6 -6
  701. package/src/modules/{workflow → version-control}/git/__tests__/git-test-helpers.ts +3 -3
  702. package/src/modules/{workflow → version-control}/git/__tests__/git.service.accessGating.test.ts +5 -5
  703. package/src/modules/{workflow → version-control}/git/__tests__/git.service.appliedChange.test.ts +4 -4
  704. package/src/modules/{workflow → version-control}/git/__tests__/git.service.changedFilesForPr.test.ts +16 -16
  705. package/src/modules/{workflow → version-control}/git/__tests__/git.service.changedPathsMemo.test.ts +2 -2
  706. package/src/modules/{workflow → version-control}/git/__tests__/git.service.commitChanges.test.ts +3 -3
  707. package/src/modules/{workflow → version-control}/git/__tests__/git.service.commitFile.strayPath.test.ts +10 -10
  708. package/src/modules/{workflow → version-control}/git/__tests__/git.service.commitFile.test.ts +3 -3
  709. package/src/modules/{workflow → version-control}/git/__tests__/git.service.createBranch.test.ts +3 -3
  710. package/src/modules/{workflow → version-control}/git/__tests__/git.service.deleteBranch.test.ts +7 -7
  711. package/src/modules/{workflow → version-control}/git/__tests__/git.service.diffFileAtCommit.test.ts +9 -9
  712. package/src/modules/{workflow → version-control}/git/__tests__/git.service.diffFileBetweenBranches.test.ts +23 -23
  713. package/src/modules/{workflow → version-control}/git/__tests__/git.service.fileBytesAtCommit.test.ts +7 -7
  714. package/src/modules/{workflow → version-control}/git/__tests__/git.service.fileContentsAtCommit.test.ts +3 -3
  715. package/src/modules/{workflow → version-control}/git/__tests__/git.service.forkPoint.test.ts +2 -2
  716. package/src/modules/{workflow → version-control}/git/__tests__/git.service.history-guards.test.ts +2 -2
  717. package/src/modules/{workflow → version-control}/git/__tests__/git.service.list-branches.test.ts +12 -12
  718. package/src/modules/{workflow → version-control}/git/__tests__/git.service.listTrackedFiles.test.ts +2 -2
  719. package/src/modules/{workflow → version-control}/git/__tests__/git.service.logForFile.test.ts +6 -6
  720. package/src/modules/{workflow → version-control}/git/__tests__/git.service.mergeChangeRequest.test.ts +20 -20
  721. package/src/modules/{workflow → version-control}/git/__tests__/git.service.mergeIntoBranch.test.ts +16 -16
  722. package/src/modules/{workflow → version-control}/git/__tests__/git.service.pendingChanges.test.ts +2 -2
  723. package/src/modules/{workflow → version-control}/git/__tests__/git.service.postMergePull.test.ts +5 -5
  724. package/src/modules/{workflow → version-control}/git/__tests__/git.service.prFetchFailure.test.ts +2 -2
  725. package/src/modules/{workflow → version-control}/git/__tests__/git.service.pull.test.ts +12 -12
  726. package/src/modules/{workflow → version-control}/git/__tests__/git.service.readFileAtRef.test.ts +3 -3
  727. package/src/modules/{workflow → version-control}/git/__tests__/git.service.revertPathsAndPush.test.ts +2 -2
  728. package/src/modules/{workflow → version-control}/git/__tests__/git.service.status.test.ts +4 -4
  729. package/src/modules/{workflow → version-control}/git/__tests__/git.service.syncFromRemote.test.ts +8 -8
  730. package/src/modules/{workflow → version-control}/git/__tests__/git.service.worktrees.test.ts +4 -4
  731. package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.list-fetch.test.ts +2 -2
  732. package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.test.ts +6 -6
  733. package/src/modules/{workflow → version-control}/git/git.service.ts +57 -57
  734. package/src/modules/{workflow → version-control}/git/node-git-runner.ts +1 -1
  735. package/src/modules/{workflow → version-control}/git/pull-request.service.ts +12 -12
  736. package/src/modules/{workflow → version-control}/index.ts +6 -5
  737. package/src/modules/{workflow → version-control}/pending-commits.service.ts +2 -2
  738. package/src/modules/{workflow → version-control}/pending-commits.worker.ts +8 -8
  739. package/src/modules/{workflow → version-control}/read-on-branch.ts +2 -2
  740. package/src/modules/{workflow/workflow-hooks.ts → version-control/version-control-hooks.ts} +3 -3
  741. package/src/modules/{workflow/workflow.routes.ts → version-control/version-control.routes.ts} +64 -63
  742. package/src/modules/{workflow/workflow.service.ts → version-control/version-control.service.ts} +92 -97
  743. package/src/modules/workspace/__tests__/agent-access.gate.test.ts +8 -8
  744. package/src/modules/workspace/__tests__/agent-downloads.test.ts +13 -16
  745. package/src/modules/workspace/__tests__/agent-uploads.test.ts +11 -12
  746. package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +11 -16
  747. package/src/modules/workspace/__tests__/file-stat-access.test.ts +3 -3
  748. package/src/modules/workspace/__tests__/git-internals.security.test.ts +27 -27
  749. package/src/modules/workspace/__tests__/kb-repository.test.ts +1 -1
  750. package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +3 -3
  751. package/src/modules/workspace/__tests__/start-session.default-branch.test.ts +152 -0
  752. package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +6 -6
  753. package/src/modules/workspace/__tests__/workspace-tmp.tools.test.ts +13 -14
  754. package/src/modules/workspace/__tests__/workspace.routes.at-ref.test.ts +6 -6
  755. package/src/modules/workspace/__tests__/workspace.routes.branch-errors.test.ts +4 -4
  756. package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +6 -6
  757. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +8 -8
  758. package/src/modules/workspace/__tests__/workspace.routes.download-implies-read.test.ts +4 -4
  759. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +6 -6
  760. package/src/modules/workspace/__tests__/workspace.routes.file-writes.test.ts +6 -6
  761. package/src/modules/workspace/__tests__/workspace.routes.folder-zip-access.test.ts +4 -4
  762. package/src/modules/workspace/__tests__/workspace.routes.folders.test.ts +6 -6
  763. package/src/modules/workspace/__tests__/workspace.routes.move-platform-files.test.ts +7 -7
  764. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +13 -13
  765. package/src/modules/workspace/__tests__/workspace.service.any-workspace-credentials.test.ts +1 -1
  766. package/src/modules/workspace/__tests__/workspace.service.replaced-repository.test.ts +1 -1
  767. package/src/modules/workspace/__tests__/workspace.service.worktrees.test.ts +1 -1
  768. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +12 -14
  769. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +5 -5
  770. package/src/modules/workspace/__tests__/workspace.tools.test.ts +182 -75
  771. package/src/modules/workspace/agent-access.gate.ts +6 -6
  772. package/src/modules/workspace/git-internals.middleware.ts +2 -2
  773. package/src/modules/workspace/kb-repository.ts +1 -1
  774. package/src/modules/workspace/startup/__tests__/kb-startup-runner.degraded.test.ts +1 -1
  775. package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +1 -1
  776. package/src/modules/workspace/startup/__tests__/kb-startup-runner.worktrees.test.ts +1 -1
  777. package/src/modules/workspace/startup/kb-git.ts +1 -1
  778. package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +94 -70
  779. package/src/modules/workspace/startup/steps/seed-tree.ts +4 -5
  780. package/src/modules/workspace/startup/steps/template-files.step.ts +4 -5
  781. package/src/modules/workspace/startup/steps/template-source.ts +21 -6
  782. package/src/modules/workspace/workspace-tmp.ts +4 -4
  783. package/src/modules/workspace/workspace.routes.ts +26 -26
  784. package/src/modules/workspace/workspace.service.ts +11 -11
  785. package/src/modules/workspace/workspace.tools.ts +90 -30
  786. package/src/shared/__tests__/canonical-file-identity.test.ts +5 -5
  787. package/src/shared/__tests__/http-errors.test.ts +4 -4
  788. package/src/shared/__tests__/read-capped.test.ts +68 -0
  789. package/src/shared/canonical-file-identity.ts +5 -5
  790. package/src/shared/domain-errors.ts +44 -44
  791. package/src/shared/git.contract.ts +1 -1
  792. package/src/shared/http-errors.ts +3 -3
  793. package/src/shared/kb-context.ts +130 -130
  794. package/src/shared/oauth-token-error.ts +46 -0
  795. package/src/shared/pending-proposals.ts +4 -4
  796. package/src/shared/read-capped.ts +40 -0
  797. package/src/tenancy/__tests__/tenant-host.test.ts +372 -372
  798. package/src/tenancy/__tests__/tenant-runtime.test.ts +221 -221
  799. package/src/tenancy/__tests__/tenant-secrets.test.ts +35 -35
  800. package/src/tenancy/index.ts +35 -35
  801. package/src/tenancy/static-tenant-source.ts +1 -1
  802. package/src/tenancy/tenant-host.ts +261 -261
  803. package/src/tenancy/tenant-runtime.ts +208 -208
  804. package/src/tenancy/tenant-source.contract.ts +59 -59
  805. package/agent-guide/finding-things.md +0 -7
  806. package/agent-guide/introduction.md +0 -27
  807. package/dist/modules/workflow/agent-tools/change-request-read-shape.d.ts.map +0 -1
  808. package/dist/modules/workflow/agent-tools/change-request-read-shape.js.map +0 -1
  809. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +0 -1
  810. package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +0 -1
  811. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +0 -1
  812. package/dist/modules/workflow/agent-tools/change-request-summary.js.map +0 -1
  813. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +0 -1
  814. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +0 -1
  815. package/dist/modules/workflow/change-request-hooks.d.ts.map +0 -1
  816. package/dist/modules/workflow/change-request-hooks.js.map +0 -1
  817. package/dist/modules/workflow/event-bus.d.ts.map +0 -1
  818. package/dist/modules/workflow/event-bus.js.map +0 -1
  819. package/dist/modules/workflow/events-delivery.d.ts.map +0 -1
  820. package/dist/modules/workflow/events-delivery.js.map +0 -1
  821. package/dist/modules/workflow/events.routes.d.ts.map +0 -1
  822. package/dist/modules/workflow/events.routes.js.map +0 -1
  823. package/dist/modules/workflow/file-lock.service.d.ts.map +0 -1
  824. package/dist/modules/workflow/file-lock.service.js.map +0 -1
  825. package/dist/modules/workflow/git/change-request-link.d.ts.map +0 -1
  826. package/dist/modules/workflow/git/change-request-link.js.map +0 -1
  827. package/dist/modules/workflow/git/git-version.d.ts.map +0 -1
  828. package/dist/modules/workflow/git/git-version.js.map +0 -1
  829. package/dist/modules/workflow/git/git.service.d.ts.map +0 -1
  830. package/dist/modules/workflow/git/git.service.js.map +0 -1
  831. package/dist/modules/workflow/git/merge-commit.d.ts.map +0 -1
  832. package/dist/modules/workflow/git/merge-commit.js.map +0 -1
  833. package/dist/modules/workflow/git/node-git-runner.d.ts.map +0 -1
  834. package/dist/modules/workflow/git/node-git-runner.js.map +0 -1
  835. package/dist/modules/workflow/git/pull-request.service.d.ts.map +0 -1
  836. package/dist/modules/workflow/git/pull-request.service.js.map +0 -1
  837. package/dist/modules/workflow/index.d.ts.map +0 -1
  838. package/dist/modules/workflow/index.js.map +0 -1
  839. package/dist/modules/workflow/pending-commits.service.d.ts.map +0 -1
  840. package/dist/modules/workflow/pending-commits.service.js.map +0 -1
  841. package/dist/modules/workflow/pending-commits.worker.d.ts.map +0 -1
  842. package/dist/modules/workflow/pending-commits.worker.js.map +0 -1
  843. package/dist/modules/workflow/read-on-branch.d.ts.map +0 -1
  844. package/dist/modules/workflow/read-on-branch.js.map +0 -1
  845. package/dist/modules/workflow/recovery-bot.d.ts.map +0 -1
  846. package/dist/modules/workflow/recovery-bot.js.map +0 -1
  847. package/dist/modules/workflow/review-workflow/approval-lock.d.ts.map +0 -1
  848. package/dist/modules/workflow/review-workflow/approval-lock.js.map +0 -1
  849. package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts.map +0 -1
  850. package/dist/modules/workflow/review-workflow/review-workflow.interface.js +0 -2
  851. package/dist/modules/workflow/review-workflow/review-workflow.interface.js.map +0 -1
  852. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +0 -1
  853. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +0 -1
  854. package/dist/modules/workflow/sanitize-error.d.ts.map +0 -1
  855. package/dist/modules/workflow/sanitize-error.js.map +0 -1
  856. package/dist/modules/workflow/workflow-hooks.d.ts.map +0 -1
  857. package/dist/modules/workflow/workflow-hooks.js.map +0 -1
  858. package/dist/modules/workflow/workflow.routes.d.ts.map +0 -1
  859. package/dist/modules/workflow/workflow.routes.js.map +0 -1
  860. package/dist/modules/workflow/workflow.service.d.ts.map +0 -1
  861. package/dist/modules/workflow/workflow.service.js.map +0 -1
  862. package/src/modules/mcp/__tests__/read-only-hint.e2e.test.ts +0 -213
  863. /package/dist/modules/{workflow → version-control}/agent-tools/change-request-read-shape.d.ts +0 -0
  864. /package/dist/modules/{workflow → version-control}/agent-tools/change-request-read-shape.js +0 -0
  865. /package/dist/modules/{workflow → version-control}/agent-tools/change-request-read.tools.d.ts +0 -0
  866. /package/dist/modules/{workflow → version-control}/agent-tools/change-request-summary.d.ts +0 -0
  867. /package/dist/modules/{workflow → version-control}/agent-tools/change-request-summary.js +0 -0
  868. /package/dist/modules/{workflow → version-control}/change-request-hooks.js +0 -0
  869. /package/dist/modules/{workflow/review-workflow → version-control/change-request-review}/approval-lock.d.ts +0 -0
  870. /package/dist/modules/{workflow/review-workflow → version-control/change-request-review}/approval-lock.js +0 -0
  871. /package/dist/modules/{workflow → version-control}/events-delivery.js +0 -0
  872. /package/dist/modules/{workflow → version-control}/events.routes.js +0 -0
  873. /package/dist/modules/{workflow → version-control}/git/change-request-link.d.ts +0 -0
  874. /package/dist/modules/{workflow → version-control}/git/change-request-link.js +0 -0
  875. /package/dist/modules/{workflow → version-control}/git/git-version.d.ts +0 -0
  876. /package/dist/modules/{workflow → version-control}/git/git-version.js +0 -0
  877. /package/dist/modules/{workflow → version-control}/git/merge-commit.d.ts +0 -0
  878. /package/dist/modules/{workflow → version-control}/git/merge-commit.js +0 -0
  879. /package/dist/modules/{workflow → version-control}/git/node-git-runner.d.ts +0 -0
  880. /package/dist/modules/{workflow → version-control}/recovery-bot.d.ts +0 -0
  881. /package/dist/modules/{workflow → version-control}/recovery-bot.js +0 -0
  882. /package/dist/modules/{workflow → version-control}/sanitize-error.d.ts +0 -0
  883. /package/dist/modules/{workflow → version-control}/sanitize-error.js +0 -0
  884. /package/src/modules/{workflow → version-control}/__tests__/change-request-hooks.test.ts +0 -0
  885. /package/src/modules/{workflow → version-control}/__tests__/fake-file-lock-db.ts +0 -0
  886. /package/src/modules/{workflow → version-control}/__tests__/pending-commits.canonical.test.ts +0 -0
  887. /package/src/modules/{workflow → version-control}/__tests__/pending-commits.enqueue-if-absent.test.ts +0 -0
  888. /package/src/modules/{workflow → version-control}/__tests__/pending-commits.repository-replaced.test.ts +0 -0
  889. /package/src/modules/{workflow → version-control}/__tests__/sanitize-error.test.ts +0 -0
  890. /package/src/modules/{workflow → version-control}/agent-tools/__tests__/change-request-read-shape.test.ts +0 -0
  891. /package/src/modules/{workflow → version-control}/agent-tools/change-request-read-shape.ts +0 -0
  892. /package/src/modules/{workflow → version-control}/agent-tools/change-request-summary.ts +0 -0
  893. /package/src/modules/{workflow/review-workflow → version-control/change-request-review}/approval-lock.ts +0 -0
  894. /package/src/modules/{workflow → version-control}/git/__tests__/branchAuthor.test.ts +0 -0
  895. /package/src/modules/{workflow → version-control}/git/__tests__/change-request-link.test.ts +0 -0
  896. /package/src/modules/{workflow → version-control}/git/__tests__/git-version.failure.test.ts +0 -0
  897. /package/src/modules/{workflow → version-control}/git/__tests__/git-version.test.ts +0 -0
  898. /package/src/modules/{workflow → version-control}/git/__tests__/node-git-runner.test.ts +0 -0
  899. /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.getPrDetail.test.ts +0 -0
  900. /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.placeholder.test.ts +0 -0
  901. /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.viewer-can-cancel.test.ts +0 -0
  902. /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.viewer-can-delete.test.ts +0 -0
  903. /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.viewer-can-update.test.ts +0 -0
  904. /package/src/modules/{workflow → version-control}/git/change-request-link.ts +0 -0
  905. /package/src/modules/{workflow → version-control}/git/git-version.ts +0 -0
  906. /package/src/modules/{workflow → version-control}/git/merge-commit.ts +0 -0
  907. /package/src/modules/{workflow → version-control}/recovery-bot.ts +0 -0
  908. /package/src/modules/{workflow → version-control}/sanitize-error.ts +0 -0
@@ -1,11 +1,15 @@
1
1
  import { createHash, randomBytes } from 'node:crypto';
2
+ import { isDeepStrictEqual } from 'node:util';
2
3
  import { logger } from '../../shared/logging.js';
3
4
  const log = logger('vault');
4
5
  import { and, desc, eq, inArray, isNull, like, or } from 'drizzle-orm';
5
- import { secrets } from '../database/index.js';
6
+ import { pgErrorCode, secrets } from '../database/index.js';
6
7
  import { TokenCrypto } from '../../shared/token-crypto.js';
7
8
  import { assertSafeFetchUrl } from '../../shared/ssrf.js';
9
+ import { readCapped } from '../../shared/read-capped.js';
10
+ import { standardTokenErrorCode } from '../../shared/oauth-token-error.js';
8
11
  import { isKeyInNamespace, InvalidSecretError, SecretNotFoundError, SecretOAuthError, } from './secrets-vault.contract.js';
12
+ import { timingSafeStringEqual } from '../auth/index.js';
9
13
  const MAX_KEY_LEN = 200;
10
14
  const MAX_LABEL_LEN = 200;
11
15
  const MAX_VALUE_LEN = 100_000;
@@ -13,6 +17,99 @@ const MAX_VALUE_LEN = 100_000;
13
17
  const REFRESH_SKEW_MS = 60_000;
14
18
  /** Upper bound on a token-endpoint round-trip, so a hung provider can't block callers. */
15
19
  const TOKEN_REQUEST_TIMEOUT_MS = 10_000;
20
+ /** Upper bound on telling a provider a managed sign-in is over, so a deletion never waits on it. */
21
+ const REVOKE_TIMEOUT_MS = 10_000;
22
+ /** Upper bound on a broker renewing a managed token: past it the stale token is served and the next call asks again. */
23
+ const REFRESH_TIMEOUT_MS = 15_000;
24
+ /** How many times a guarded replacement re-reads a row that changed under its revoke before giving up. */
25
+ const REPLACE_ATTEMPTS = 3;
26
+ /** How many times a handoff re-reads a row a refresh rotated under it (the nonce rides along) before refusing. */
27
+ const HANDOFF_ATTEMPTS = 3;
28
+ /** `work`, or a rejection once `ms` have passed: a handler that never settles must not hold the vault. */
29
+ async function bounded(work, ms, what) {
30
+ let timer;
31
+ try {
32
+ return await Promise.race([
33
+ work,
34
+ new Promise((_, reject) => {
35
+ timer = setTimeout(() => reject(new Error(`${what} did not settle within ${ms}ms`)), ms);
36
+ }),
37
+ ]);
38
+ }
39
+ finally {
40
+ if (timer)
41
+ clearTimeout(timer);
42
+ }
43
+ }
44
+ /** How often a managed sign-in's opening is retried against a row that keeps changing under it. */
45
+ const BEGIN_MANAGED_ATTEMPTS = 3;
46
+ /** How much of a refusing token endpoint's body is read for its error code: an OAuth error object, never a whole error page. */
47
+ const TOKEN_ERROR_BODY_MAX_BYTES = 2048;
48
+ // Hexis's own sentences for a refused token request: what to change, in our
49
+ // words, never the provider's.
50
+ const HINT_CLIENT = 'The client id or client secret this sign-in uses is not accepted by the provider. The tool owner should check the declared client id and set the client secret again.';
51
+ const HINT_GRANT = 'The provider refused the grant: for a sign-in, a redirect URL mismatch or an expired or reused authorization code; for a refresh, a refresh token that has expired or been revoked. Sign in again.';
52
+ const HINT_SCOPE = "The provider refused the scopes this sign-in asks for. The tool owner should check the declared scopes against the provider's app.";
53
+ const HINT_UNAUTHORIZED = "The provider does not allow this client to use this grant. The tool owner should check the provider's app settings.";
54
+ const HINT_REQUEST = 'The provider found the token request malformed. The tool owner should check the declared endpoints and parameters.';
55
+ const HINT_PROVIDER = 'The provider could not serve the token request. Try again later.';
56
+ /**
57
+ * Hexis's sentence for each error code a refusing token endpoint may answer:
58
+ * the standard ones (`shared/oauth-token-error.ts` is the one list of those,
59
+ * read here and by the OIDC configuration check) in `error`, and the ones
60
+ * HubSpot's token endpoint answers in its `status` field. Only a code from
61
+ * those lists is ever taken, exactly, so no other string a provider puts in a
62
+ * field — a secret, a secret with a suffix, free text — can reach a message.
63
+ * Nothing else of the body is ever taken: no description, no message, no first
64
+ * line. That is why a message built here needs no scrubbing, cutting or
65
+ * decoding, and why every spelling a secret could reach a log in (escaped,
66
+ * encoded, cut) stays inexpressible. A standard code without a sentence here
67
+ * (`access_denied`) is named and left unexplained.
68
+ */
69
+ const TOKEN_ERROR_HINTS = new Map([
70
+ ['invalid_request', HINT_REQUEST],
71
+ ['invalid_client', HINT_CLIENT],
72
+ ['invalid_grant', HINT_GRANT],
73
+ ['unauthorized_client', HINT_UNAUTHORIZED],
74
+ ['unsupported_grant_type', HINT_UNAUTHORIZED],
75
+ ['invalid_scope', HINT_SCOPE],
76
+ ['server_error', HINT_PROVIDER],
77
+ ['temporarily_unavailable', HINT_PROVIDER],
78
+ ['BAD_CLIENT_ID', HINT_CLIENT],
79
+ ['BAD_CLIENT_SECRET', HINT_CLIENT],
80
+ ['BAD_AUTH_CODE', HINT_GRANT],
81
+ ['BAD_REDIRECT_URI', HINT_GRANT],
82
+ ]);
83
+ /** The codes HubSpot's token endpoint answers in `status` instead of a standard `error`. */
84
+ const HUBSPOT_STATUS_CODES = new Set(['BAD_CLIENT_ID', 'BAD_CLIENT_SECRET', 'BAD_AUTH_CODE', 'BAD_REDIRECT_URI']);
85
+ /**
86
+ * The known error code a refusing token endpoint answered, or null: a standard
87
+ * `error` (RFC 6749 §5.2), else HubSpot's own `status`, when it is exactly one
88
+ * of the codes known. Without even the code, a bare "HTTP 400" cannot tell a
89
+ * client the provider does not accept from a refused grant.
90
+ */
91
+ export function tokenEndpointErrorCode(bodyText) {
92
+ const standard = standardTokenErrorCode(bodyText);
93
+ if (standard !== null)
94
+ return standard;
95
+ let json;
96
+ try {
97
+ json = JSON.parse(bodyText);
98
+ }
99
+ catch {
100
+ return null;
101
+ }
102
+ if (!json || typeof json !== 'object' || Array.isArray(json))
103
+ return null;
104
+ const { status } = json;
105
+ return typeof status === 'string' && HUBSPOT_STATUS_CODES.has(status) ? status : null;
106
+ }
107
+ /** The refusal in one message: the status, the known code when there is one, and Hexis's sentence for it. */
108
+ export function describeTokenEndpointRefusal(status, code) {
109
+ const base = `Token endpoint returned HTTP ${status}`;
110
+ const hint = code === null ? undefined : TOKEN_ERROR_HINTS.get(code);
111
+ return hint ? `${base} (${code}). ${hint}` : base;
112
+ }
16
113
  /**
17
114
  * How a row is stored, or `null` when there is no row — the column is a plain
18
115
  * string, and anything that is not `oauth` is a stored value (`toSummary` reads
@@ -41,6 +138,19 @@ export class DbSecretsVaultService {
41
138
  now;
42
139
  scopeOf;
43
140
  cryptoInstance = null;
141
+ /**
142
+ * How a MANAGED sign-in's tokens are renewed and revoked — through the
143
+ * broker and the provider respectively. Setter-injected: both depend on
144
+ * the connector catalog, which is built after this service. Without them a
145
+ * managed token that expires is served stale (a transient, retried on the
146
+ * next call) and a disconnect deletes without revoking.
147
+ */
148
+ managedRefresher;
149
+ managedRevoker;
150
+ setManagedTokenHandlers(handlers) {
151
+ this.managedRefresher = handlers.refresher;
152
+ this.managedRevoker = handlers.revoker;
153
+ }
44
154
  constructor(db,
45
155
  /** 32-byte AES key (hex/base64); empty disables secret read/write with a clear error. */
46
156
  encKey, now = Date.now,
@@ -108,15 +218,18 @@ export class DbSecretsVaultService {
108
218
  }
109
219
  const label = this.normaliseLabel(input.label);
110
220
  const valueEncrypted = this.crypto().encrypt(input.value);
221
+ // The caller's row may hold a managed sign-in: its tokens are told to the
222
+ // provider before this value replaces them, as on every replacement.
111
223
  // Upsert on (user_id, key): re-saving a key replaces its value in place.
112
- const [row] = await this.db
224
+ const row = await this.revokedReplacement(and(eq(secrets.userId, userId), eq(secrets.key, key)), (guard) => this.db
113
225
  .insert(secrets)
114
226
  .values({ userId, key, kind: 'static', label, valueEncrypted, oauthMeta: null })
115
227
  .onConflictDoUpdate({
116
228
  target: [secrets.userId, secrets.key],
117
229
  set: { kind: 'static', label, valueEncrypted, oauthMeta: null, updatedAt: new Date() },
230
+ ...(guard ? { setWhere: guard } : {}),
118
231
  })
119
- .returning();
232
+ .returning());
120
233
  this.notifyMutation(userId);
121
234
  return this.toSummary(row);
122
235
  }
@@ -128,7 +241,9 @@ export class DbSecretsVaultService {
128
241
  const blob = { clientSecret: provider.clientSecret };
129
242
  const valueEncrypted = this.crypto().encrypt(JSON.stringify(blob));
130
243
  const oauthMeta = this.publicProviderMeta(provider);
131
- const [row] = await this.db
244
+ // A managed sign-in under this key loses its tokens here: the provider is
245
+ // told first, as on every replacement.
246
+ const row = await this.revokedReplacement(and(eq(secrets.userId, userId), eq(secrets.key, key)), (guard) => this.db
132
247
  .insert(secrets)
133
248
  .values({ userId, key, kind: 'oauth', label, valueEncrypted, oauthMeta })
134
249
  .onConflictDoUpdate({
@@ -136,17 +251,15 @@ export class DbSecretsVaultService {
136
251
  // Replacing an oauth secret's provider config drops any existing tokens
137
252
  // (they belonged to the old client) — the user must re-authorize.
138
253
  set: { kind: 'oauth', label, valueEncrypted, oauthMeta, updatedAt: new Date() },
254
+ ...(guard ? { setWhere: guard } : {}),
139
255
  })
140
- .returning();
256
+ .returning());
141
257
  this.notifyMutation(userId);
142
258
  return this.toSummary(row);
143
259
  }
144
260
  async remove(userId, id) {
145
- const res = await this.db
146
- .delete(secrets)
147
- .where(and(eq(secrets.id, id), eq(secrets.userId, userId)))
148
- .returning({ id: secrets.id });
149
- if (res.length === 0)
261
+ const gone = await this.revokeThenDelete(and(eq(secrets.id, id), eq(secrets.userId, userId)));
262
+ if (gone.length === 0)
150
263
  throw new SecretNotFoundError(id);
151
264
  this.notifyMutation(userId);
152
265
  }
@@ -160,38 +273,186 @@ export class DbSecretsVaultService {
160
273
  }
161
274
  const label = this.normaliseLabel(input.label);
162
275
  const valueEncrypted = this.crypto().encrypt(input.value);
276
+ // A shared row that held a managed workspace connection is told to its
277
+ // provider before this value replaces it; the tokens would otherwise go
278
+ // unrevoked with the row's kind.
163
279
  // Upsert the ONE shared row for this key (user_id NULL). The conflict target
164
280
  // is the partial unique index `secrets_shared_key_unq` — hence `targetWhere`.
165
- const [row] = await this.db
281
+ const row = await this.revokedReplacement(and(isNull(secrets.userId), eq(secrets.key, key)), (guard) => this.db
166
282
  .insert(secrets)
167
283
  .values({ userId: null, key, kind: 'static', label, valueEncrypted, oauthMeta: null })
168
284
  .onConflictDoUpdate({
169
285
  target: secrets.key,
170
286
  targetWhere: isNull(secrets.userId),
171
287
  set: { kind: 'static', label, valueEncrypted, oauthMeta: null, updatedAt: new Date() },
288
+ ...(guard ? { setWhere: guard } : {}),
172
289
  })
173
- .returning();
290
+ .returning());
174
291
  this.notifyMutation(null);
175
292
  return this.toSummary(row);
176
293
  }
177
294
  async removeShared(key) {
178
- const res = await this.db
179
- .delete(secrets)
180
- .where(and(isNull(secrets.userId), eq(secrets.key, key)))
181
- .returning({ id: secrets.id });
182
- if (res.length === 0)
295
+ const gone = await this.revokeThenDelete(and(isNull(secrets.userId), eq(secrets.key, key)));
296
+ if (gone.length === 0)
183
297
  throw new SecretNotFoundError(key);
184
298
  this.notifyMutation(null);
185
299
  }
186
300
  async removeUserByKey(userId, key) {
187
- const res = await this.db
188
- .delete(secrets)
189
- .where(and(eq(secrets.userId, userId), eq(secrets.key, key)))
190
- .returning({ id: secrets.id });
191
- if (res.length === 0)
301
+ const gone = await this.revokeThenDelete(and(eq(secrets.userId, userId), eq(secrets.key, key)));
302
+ if (gone.length === 0)
192
303
  throw new SecretNotFoundError(key);
193
304
  this.notifyMutation(userId);
194
305
  }
306
+ /**
307
+ * Delete the rows `where` names, each told to its provider first when it
308
+ * is a managed sign-in with tokens (see {@link revokeRow}). The delete is
309
+ * guarded on the ciphertext the revoke read: a handoff or a refresh landing
310
+ * in between has replaced the tokens, and deleting THOSE unrevoked is the
311
+ * leak the revoke exists to prevent. A miss re-reads the row and goes
312
+ * again, bounded; what could not be settled in those passes is left
313
+ * standing rather than deleted blind.
314
+ */
315
+ async revokeThenDelete(where) {
316
+ // No revoker, nothing to read first: one plain delete, as every removal
317
+ // was before managed sign-ins, and no miss to re-read for.
318
+ if (!this.managedRevoker) {
319
+ return this.db.delete(secrets).where(where).returning({ id: secrets.id, userId: secrets.userId, kind: secrets.kind });
320
+ }
321
+ const gone = [];
322
+ for (let attempt = 0; attempt < 3; attempt++) {
323
+ const rows = await this.db.select().from(secrets).where(where);
324
+ if (rows.length === 0)
325
+ break;
326
+ // Every row's revocation at once, so a bulk delete (a namespace, a
327
+ // person's connections) waits one revocation's bound, not one per row.
328
+ await Promise.all(rows.map((row) => this.revokeRow(row)));
329
+ let missed = false;
330
+ for (const row of rows) {
331
+ const deleted = await this.db
332
+ .delete(secrets)
333
+ .where(and(eq(secrets.id, row.id), eq(secrets.valueEncrypted, row.valueEncrypted)))
334
+ .returning({ id: secrets.id, userId: secrets.userId, kind: secrets.kind });
335
+ if (deleted.length === 0)
336
+ missed = true;
337
+ else
338
+ gone.push(...deleted);
339
+ }
340
+ if (!missed)
341
+ break;
342
+ }
343
+ return gone;
344
+ }
345
+ async removeManagedRowsOfUser(userId, within) {
346
+ if (within)
347
+ return this.removeManagedRowsLocked(userId, within);
348
+ // Every oauth row of the user whose meta names a managed app. Read
349
+ // first (the managed mark lives in JSON the WHERE cannot see), revoked,
350
+ // then deleted by id; the per-user filter on the delete is what keeps a
351
+ // row read for this user from ever deleting another's.
352
+ const rows = await this.db
353
+ .select()
354
+ .from(secrets)
355
+ .where(and(eq(secrets.userId, userId), eq(secrets.kind, 'oauth')));
356
+ const managed = rows.filter((r) => this.managedRefOf(r.oauthMeta) !== null);
357
+ if (managed.length === 0)
358
+ return 0;
359
+ const deleted = await this.revokeThenDelete(and(eq(secrets.userId, userId), inArray(secrets.id, managed.map((r) => r.id))));
360
+ if (deleted.length > 0)
361
+ this.notifyMutation(userId);
362
+ // A row the guarded delete could not settle (its tokens kept changing
363
+ // under the revoke) is still here, unrevoked. The caller is told rather
364
+ // than left to believe the person holds nothing: an erasure refuses and
365
+ // is retried, a switch-off logs it and the row is refused on use.
366
+ if (deleted.length < managed.length) {
367
+ throw new SecretOAuthError(`${managed.length - deleted.length} managed sign-in(s) kept changing while being revoked and were not removed; try again`);
368
+ }
369
+ return deleted.length;
370
+ }
371
+ /**
372
+ * The same removal inside a caller's transaction (an account erasure, just
373
+ * before the user row goes): the rows are LOCKED for the transaction, so a
374
+ * handoff or a refresh arriving now waits and then finds no row, which is
375
+ * what makes the revoke final. No guard is needed under the lock; the
376
+ * revoke stays bounded (see {@link revokeRow}), so the transaction never
377
+ * waits on a provider longer than that.
378
+ */
379
+ async removeManagedRowsLocked(userId, tx) {
380
+ const rows = await tx
381
+ .select()
382
+ .from(secrets)
383
+ .where(and(eq(secrets.userId, userId), eq(secrets.kind, 'oauth')))
384
+ .for('update');
385
+ const managed = rows.filter((r) => this.managedRefOf(r.oauthMeta) !== null);
386
+ if (managed.length === 0)
387
+ return 0;
388
+ await Promise.all(managed.map((row) => this.revokeRow(row)));
389
+ await tx.delete(secrets).where(and(eq(secrets.userId, userId), inArray(secrets.id, managed.map((r) => r.id))));
390
+ this.notifyMutation(userId);
391
+ return managed.length;
392
+ }
393
+ /**
394
+ * Replace what `where` names with `write`, a managed sign-in's tokens told
395
+ * to their provider first (see {@link revokeRow}; best effort, never holding
396
+ * the user's decision). The write is guarded on the ciphertext the revoke
397
+ * read (`setWhere`): a refresh or a handoff landing in between has rotated
398
+ * the tokens, and writing over THOSE unrevoked is the leak the revoke exists
399
+ * to prevent — as the guarded delete of {@link revokeThenDelete}. A miss
400
+ * re-reads and goes again, bounded; a row that kept changing is left as it
401
+ * is and the caller is told. With no revoker wired there is nothing to read
402
+ * first: one plain write, as every replacement was before managed sign-ins.
403
+ */
404
+ async revokedReplacement(where, write) {
405
+ if (!this.managedRevoker)
406
+ return (await write())[0];
407
+ for (let attempt = 0; attempt < REPLACE_ATTEMPTS; attempt++) {
408
+ const rows = await this.db.select().from(secrets).where(where);
409
+ await Promise.all(rows.map((row) => this.revokeRow(row)));
410
+ // The row as it was read, or no row at all: a conflict with anything
411
+ // else (a row written meanwhile) is a miss, and the next pass reads it.
412
+ const guard = rows.length > 0
413
+ ? inArray(secrets.valueEncrypted, rows.map((row) => row.valueEncrypted))
414
+ : eq(secrets.valueEncrypted, '');
415
+ const written = await write(guard);
416
+ if (written.length > 0)
417
+ return written[0];
418
+ }
419
+ throw new SecretOAuthError('the sign-in kept changing while being replaced; try again');
420
+ }
421
+ async revokeRow(row) {
422
+ if (!this.managedRevoker || row.kind !== 'oauth')
423
+ return;
424
+ const ref = this.managedRefOf(row.oauthMeta);
425
+ if (!ref)
426
+ return;
427
+ const tokens = this.readTokensSafe(row.valueEncrypted);
428
+ if (!tokens?.access_token)
429
+ return;
430
+ // Bounded: the delete that follows is the user's decision, and a revoker
431
+ // that never settles must not hold it.
432
+ try {
433
+ await bounded(this.managedRevoker.revoke(ref, { access_token: tokens.access_token, refresh_token: tokens.refresh_token }), REVOKE_TIMEOUT_MS, 'revocation');
434
+ }
435
+ catch (err) {
436
+ // The key and the connector name the sign-in. The error itself stays
437
+ // out: a revoker's message is its own, and this line is the one place a
438
+ // token could otherwise reach a log from this path.
439
+ log.warn('revoking a managed sign-in with its provider failed; deleting it here anyway', {
440
+ key: row.key,
441
+ connector: ref.connectorId,
442
+ failure: err instanceof Error ? err.name : typeof err,
443
+ });
444
+ }
445
+ }
446
+ /** The managed reference a row's public meta carries, or null for an ordinary sign-in. */
447
+ managedRefOf(raw) {
448
+ const meta = (raw ?? {});
449
+ const m = meta.managed;
450
+ if (!m || typeof m !== 'object')
451
+ return null;
452
+ const connectorId = typeof m.connectorId === 'string' ? m.connectorId : '';
453
+ const scopeSet = typeof m.scopeSet === 'string' ? m.scopeSet : '';
454
+ return connectorId && scopeSet ? { connectorId, scopeSet } : null;
455
+ }
195
456
  async countNamespace(prefix) {
196
457
  return tally(await this.namespaceRows(prefix));
197
458
  }
@@ -212,10 +473,10 @@ export class DbSecretsVaultService {
212
473
  const rows = await this.namespaceRows(prefix);
213
474
  if (rows.length === 0)
214
475
  break;
215
- const deleted = await this.db
216
- .delete(secrets)
217
- .where(inArray(secrets.id, rows.map((r) => r.id)))
218
- .returning({ userId: secrets.userId, kind: secrets.kind });
476
+ // A managed sign-in under the namespace is told to its provider
477
+ // before the delete, for the same reason the per-row removals do —
478
+ // and guarded the same way, when a revoker is there to need it.
479
+ const deleted = await this.revokeThenDelete(inArray(secrets.id, rows.map((r) => r.id)));
219
480
  gone.push(...deleted);
220
481
  }
221
482
  }
@@ -265,6 +526,7 @@ export class DbSecretsVaultService {
265
526
  userId: secrets.userId,
266
527
  kind: secrets.kind,
267
528
  valueEncrypted: secrets.valueEncrypted,
529
+ oauthMeta: secrets.oauthMeta,
268
530
  })
269
531
  .from(secrets)
270
532
  .where(and(inArray(secrets.key, keys), or(isNull(secrets.userId), eq(secrets.userId, userId))));
@@ -273,6 +535,15 @@ export class DbSecretsVaultService {
273
535
  const adminRow = rows.find((r) => r.key === key && r.userId === null);
274
536
  // Decrypt+parse the oauth row's token set once, then derive both fields from it.
275
537
  const tokens = userRow?.kind === 'oauth' ? this.readTokensSafe(userRow.valueEncrypted) : undefined;
538
+ // The shared row holds tokens only for a managed workspace connection;
539
+ // an ordinary sign-in's shared row is the client secret and reads as
540
+ // "no tokens" here, which is the truthful answer for it.
541
+ const adminTokens = adminRow?.kind === 'oauth' ? this.readTokensSafe(adminRow.valueEncrypted) : undefined;
542
+ const adminMeta = (adminRow?.oauthMeta ?? null);
543
+ const accountOf = (meta) => {
544
+ const a = meta?.account;
545
+ return typeof a === 'string' && a ? a : undefined;
546
+ };
276
547
  return {
277
548
  key,
278
549
  adminConfigured: !!adminRow,
@@ -286,9 +557,154 @@ export class DbSecretsVaultService {
286
557
  // Only meaningful for an oauth row: the scopes the caller's token was granted,
287
558
  // so a coverage check can tell whether it still covers the tool's live scopes.
288
559
  grantedScopes: userRow?.kind === 'oauth' ? tokens?.scope : undefined,
560
+ adminAuthorized: adminRow?.kind === 'oauth' ? Boolean(adminTokens?.access_token) : undefined,
561
+ adminGrantedScopes: adminRow?.kind === 'oauth' ? adminTokens?.scope : undefined,
562
+ userAccount: userRow?.kind === 'oauth' ? accountOf(userRow.oauthMeta) : undefined,
563
+ adminAccount: adminRow?.kind === 'oauth' ? accountOf(adminMeta) : undefined,
564
+ // An owner's own app on the shared row: a client id without the managed mark.
565
+ adminBringsOwnApp: adminRow?.kind === 'oauth'
566
+ ? this.managedRefOf(adminMeta) === null && typeof adminMeta?.clientId === 'string' && adminMeta.clientId !== ''
567
+ : undefined,
289
568
  };
290
569
  });
291
570
  }
571
+ async beginManagedConnection(input) {
572
+ const key = this.requireKey(input.key);
573
+ const userId = input.userId === null ? null : this.requireUserId(input.userId);
574
+ const ref = input.provider.managed;
575
+ if (!ref)
576
+ throw new InvalidSecretError('a managed sign-in needs its connector reference');
577
+ if (typeof input.nonce !== 'string' || input.nonce.length < 16)
578
+ throw new InvalidSecretError('nonce is required');
579
+ const oauthMeta = this.publicProviderMeta(this.requireProvider(input.provider, { managed: true }));
580
+ const where = userId === null ? and(isNull(secrets.userId), eq(secrets.key, key)) : and(eq(secrets.userId, userId), eq(secrets.key, key));
581
+ // Read-modify-write on a LIVE credential: a refresh running concurrently
582
+ // may rotate the tokens between the read and the write, and writing our
583
+ // copy over them would leave the row holding a dead refresh token. So the
584
+ // update is guarded on the ciphertext read, and a miss re-reads and
585
+ // rebuilds from the fresh row; bounded, so a row that keeps changing
586
+ // fails loudly rather than forever. The same rule `beginOAuth` follows.
587
+ for (let attempt = 0; attempt < BEGIN_MANAGED_ATTEMPTS; attempt++) {
588
+ const [existing] = await this.db.select().from(secrets).where(where).limit(1);
589
+ // Keep what the SAME connection already holds (its tokens, so an
590
+ // abandoned reconnect costs nothing). The same connection means the same
591
+ // connector and scope set: a row of another connector's, or of another
592
+ // set's, holds a token for something else, and a declaration that
593
+ // changed must not hand it to the new one. Start clean over anything
594
+ // else too — a static value or an owner-app sign-in under this key.
595
+ const storedRef = existing?.kind === 'oauth' ? this.managedRefOf(existing.oauthMeta) : null;
596
+ const sameConnection = storedRef !== null && storedRef.connectorId === ref.connectorId && storedRef.scopeSet === ref.scopeSet;
597
+ let kept = {};
598
+ if (existing && sameConnection) {
599
+ try {
600
+ kept = this.readBlob(existing.valueEncrypted);
601
+ }
602
+ catch {
603
+ kept = {};
604
+ }
605
+ }
606
+ const next = { tokens: kept.tokens, pendingNonce: input.nonce };
607
+ const valueEncrypted = this.crypto().encrypt(JSON.stringify(next));
608
+ const account = sameConnection ? existing.oauthMeta?.account : undefined;
609
+ const meta = typeof account === 'string' && account ? { ...oauthMeta, account } : oauthMeta;
610
+ if (!existing) {
611
+ try {
612
+ await this.db.insert(secrets).values({ userId, key, kind: 'oauth', label: null, valueEncrypted, oauthMeta: meta });
613
+ return;
614
+ }
615
+ catch (err) {
616
+ // Another start of the same sign-in inserted the row first: the
617
+ // next pass finds it and updates it, as it would any existing row.
618
+ if (pgErrorCode(err) === '23505')
619
+ continue; // unique_violation
620
+ throw err;
621
+ }
622
+ }
623
+ // A row that held ANOTHER connection's tokens is about to lose them:
624
+ // the provider is told first, as on every other replacement.
625
+ if (!sameConnection && storedRef)
626
+ await this.revokeRow(existing);
627
+ const written = await this.db
628
+ .update(secrets)
629
+ .set({ kind: 'oauth', valueEncrypted, oauthMeta: meta, updatedAt: new Date() })
630
+ .where(and(eq(secrets.id, existing.id), eq(secrets.valueEncrypted, existing.valueEncrypted)))
631
+ .returning({ id: secrets.id });
632
+ if (Array.isArray(written) && written.length === 0)
633
+ continue;
634
+ // A row that held something ELSE (a value, another connection's
635
+ // tokens, an owner's app) no longer does: caches keyed on it are told.
636
+ // The same connection's tokens carried over unchanged tell nobody.
637
+ if (!kept.tokens)
638
+ this.notifyMutation(userId);
639
+ return;
640
+ }
641
+ throw new SecretOAuthError('The sign-in could not be opened: its stored credential kept changing. Try again.');
642
+ }
643
+ async completeManagedConnection(input) {
644
+ const key = this.requireKey(input.key);
645
+ const userId = input.userId === null ? null : this.requireUserId(input.userId);
646
+ const where = userId === null ? and(isNull(secrets.userId), eq(secrets.key, key)) : and(eq(secrets.userId, userId), eq(secrets.key, key));
647
+ // Every refusal below is the same words: a caller that can tell "no such
648
+ // sign-in" from "wrong nonce" from "already used" has learned which rows
649
+ // exist, and the handoff route is reachable with the broker secret alone.
650
+ const refused = () => new SecretOAuthError('No managed sign-in is waiting for this handoff');
651
+ const tokens = input.tokens;
652
+ if (typeof tokens?.access_token !== 'string' || !tokens.access_token) {
653
+ throw new InvalidSecretError('the handoff carries no access token');
654
+ }
655
+ // Read, check the nonce, write guarded on what was read; and again when
656
+ // the write misses. A miss with the nonce still on the row is a refresh
657
+ // that rotated the row's tokens meanwhile (a refresh carries the nonce
658
+ // across, see `inFlightOf`), not a second handoff: the broker delivered
659
+ // these tokens once, and refusing them would lose a valid sign-in for
660
+ // good. A row whose nonce is gone or changed refuses on the re-read.
661
+ for (let attempt = 0;; attempt++) {
662
+ const [row] = await this.db.select().from(secrets).where(where).limit(1);
663
+ if (!row || row.kind !== 'oauth' || !this.managedRefOf(row.oauthMeta))
664
+ throw refused();
665
+ let blob;
666
+ try {
667
+ blob = this.readBlob(row.valueEncrypted);
668
+ }
669
+ catch {
670
+ throw refused();
671
+ }
672
+ if (!blob.pendingNonce || typeof input.nonce !== 'string' || !timingSafeStringEqual(input.nonce, blob.pendingNonce)) {
673
+ throw refused();
674
+ }
675
+ const meta = { ...(row.oauthMeta ?? {}) };
676
+ // A broker that echoes no `scope` delivered what the sign-in asked for:
677
+ // the scopes the row was opened with stand in, as `pendingScopes` does
678
+ // for the code exchange. Stored as unknown, the grant would count as
679
+ // covering nothing, and the sign-in would read as under-scoped for good.
680
+ // An EMPTY scope is not "none echoed": it is an empty grant, kept as such.
681
+ const requested = Array.isArray(meta.scopes) && meta.scopes.length > 0 ? meta.scopes.map(String).join(' ') : undefined;
682
+ const stored = {
683
+ access_token: tokens.access_token,
684
+ refresh_token: typeof tokens.refresh_token === 'string' ? tokens.refresh_token : undefined,
685
+ expires_at: typeof tokens.expires_at === 'number' ? tokens.expires_at : undefined,
686
+ token_type: typeof tokens.token_type === 'string' ? tokens.token_type : undefined,
687
+ scope: typeof tokens.scope === 'string' ? tokens.scope : requested,
688
+ };
689
+ // The nonce is one-time: it leaves with the handoff it was minted for,
690
+ // so the same handoff presented twice finds nothing to match.
691
+ const next = { tokens: stored };
692
+ if (typeof input.account === 'string' && input.account.trim())
693
+ meta.account = input.account.trim().slice(0, MAX_LABEL_LEN);
694
+ // Guarded on the ciphertext read above: two handoffs racing for one
695
+ // nonce cannot both land, whichever the provider sent first wins.
696
+ const landed = await this.db
697
+ .update(secrets)
698
+ .set({ valueEncrypted: this.crypto().encrypt(JSON.stringify(next)), oauthMeta: meta, updatedAt: new Date() })
699
+ .where(and(eq(secrets.id, row.id), eq(secrets.valueEncrypted, row.valueEncrypted)))
700
+ .returning({ id: secrets.id });
701
+ if (!(Array.isArray(landed) && landed.length === 0))
702
+ break;
703
+ if (attempt + 1 >= HANDOFF_ATTEMPTS)
704
+ throw refused();
705
+ }
706
+ this.notifyMutation(userId);
707
+ }
292
708
  /**
293
709
  * Decrypt+parse an oauth row's token set once, or undefined if the blob can't be
294
710
  * read — a corrupted row or rotated encKey must degrade to "not authorized", not throw.
@@ -418,27 +834,35 @@ export class DbSecretsVaultService {
418
834
  throw new InvalidSecretError('clientSecret is required');
419
835
  }
420
836
  // Validate the provider config the same way createOAuth does (SSRF-safe https
421
- // URLs). Store the client secret AND the provider meta together on the shared
422
- // row so a later `.tool` edit can't redirect the secret to another endpoint.
837
+ // URLs) and store it beside the secret as a record of what was saved — what
838
+ // `statusFor` and the setup surfaces read. A declared sign-in does not run
839
+ // from it: `beginToolOAuthByKey` is handed the declaration as it reads now.
423
840
  const provider = this.requireProvider({ ...input.provider, clientSecret: input.clientSecret });
424
841
  const blob = { clientSecret: provider.clientSecret };
425
842
  const valueEncrypted = this.crypto().encrypt(JSON.stringify(blob));
426
843
  const oauthMeta = this.publicProviderMeta(provider);
427
- await this.db
844
+ // An owner's own app replacing a managed workspace connection: the
845
+ // connection's tokens are revoked with the provider before they go.
846
+ await this.revokedReplacement(and(isNull(secrets.userId), eq(secrets.key, key)), (guard) => this.db
428
847
  .insert(secrets)
429
848
  .values({ userId: null, key, kind: 'oauth', label: null, valueEncrypted, oauthMeta })
430
849
  .onConflictDoUpdate({
431
850
  target: secrets.key,
432
851
  targetWhere: isNull(secrets.userId),
433
852
  set: { kind: 'oauth', valueEncrypted, oauthMeta, updatedAt: new Date() },
434
- });
853
+ ...(guard ? { setWhere: guard } : {}),
854
+ })
855
+ .returning({ id: secrets.id }));
435
856
  this.notifyMutation(null);
436
857
  }
437
858
  async beginToolOAuthByKey(input) {
438
859
  const userId = this.requireUserId(input.userId);
439
860
  const key = this.requireKey(input.key);
440
- // The shared row holds the owner-set client secret + the provider meta it was
441
- // set for. Both are read from HERE, never from the live `.tool`.
861
+ // The shared row holds the owner-set client secret. For a client the
862
+ // platform registered itself it also holds the provider configuration, the
863
+ // only description of that client there is; for a declared sign-in the
864
+ // configuration it holds is a record of the save, and `input.provider` (the
865
+ // tool file as it reads now) is what the sign-in follows.
442
866
  const [shared] = await this.db
443
867
  .select()
444
868
  .from(secrets)
@@ -448,11 +872,47 @@ export class DbSecretsVaultService {
448
872
  throw new SecretOAuthError("This tool's owner hasn't finished setting this up");
449
873
  }
450
874
  const sharedBlob = this.readBlob(shared.valueEncrypted);
451
- const meta = this.readMeta(shared.oauthMeta);
452
- // A missing secret is "not set up yet" ONLY for confidential clients — a
453
- // dynamically-registered PUBLIC client (PKCE-only) never has one.
454
- if (!sharedBlob.clientSecret && !meta.publicClient) {
455
- throw new SecretOAuthError("This tool's owner hasn't finished setting this up");
875
+ let meta;
876
+ let oauthMeta;
877
+ if (input.provider) {
878
+ // A declared client is confidential: without the owner's secret there is
879
+ // nothing to sign in with, whatever the stored row says of itself.
880
+ if (!sharedBlob.clientSecret) {
881
+ throw new SecretOAuthError("This tool's owner hasn't finished setting this up");
882
+ }
883
+ // The save's own validation (https, no private or local address), BEFORE
884
+ // the caller's row is written or a URL built: a declaration that fails it
885
+ // sends nothing anywhere. A MANAGED declaration names no client of its
886
+ // own; reaching here means an owner pinned the deployment's own app on
887
+ // the shared row, and that app's id is the one the flow runs with.
888
+ const clientId = input.provider.clientId || this.readMeta(shared.oauthMeta).clientId;
889
+ meta = this.requireProvider({ ...input.provider, clientId, publicClient: false });
890
+ oauthMeta = this.publicProviderMeta(meta);
891
+ // A MOVED PROPERTY, decided on the ticket (OAuth-Sign-In-Follows-The-Live-
892
+ // Declaration, 2026-10-27): the secret used to be bound to the token
893
+ // endpoint pinned at its save, so a later file edit could not send it
894
+ // anywhere else; now it goes where the file says at each start, so a
895
+ // writer of the tool file can move it (to an https, public host — the
896
+ // validation above still holds). Nothing refuses that; this line is the
897
+ // trail an operator reads when a secret went somewhere new.
898
+ const recorded = (shared.oauthMeta ?? {});
899
+ if (typeof recorded.tokenUrl === 'string' && recorded.tokenUrl !== meta.tokenUrl) {
900
+ // Origins only: a path or query is the file's to say, not the log's.
901
+ const origin = (u) => (URL.canParse(u) ? new URL(u).origin : '(unparseable)');
902
+ log.warn(`sign-in on ${key}: the declared token endpoint differs from the one recorded when the client secret was saved`, {
903
+ savedOrigin: origin(recorded.tokenUrl),
904
+ declaredOrigin: origin(meta.tokenUrl),
905
+ });
906
+ }
907
+ }
908
+ else {
909
+ meta = this.readMeta(shared.oauthMeta);
910
+ oauthMeta = shared.oauthMeta;
911
+ // A missing secret is "not set up yet" ONLY for confidential clients — a
912
+ // dynamically-registered PUBLIC client (PKCE-only) never has one.
913
+ if (!sharedBlob.clientSecret && !meta.publicClient) {
914
+ throw new SecretOAuthError("This tool's owner hasn't finished setting this up");
915
+ }
456
916
  }
457
917
  // Carry forward any tokens the caller already holds so re-entering the flow
458
918
  // (or abandoning it) can't de-authorize a still-valid prior grant — only
@@ -466,10 +926,12 @@ export class DbSecretsVaultService {
466
926
  // encKey must degrade to "start fresh", not crash the recovery flow — and (b)
467
927
  // they were minted under the SAME provider config we're about to (re)write, or
468
928
  // a refresh_token for a stale client id/secret would silently fail once carried
469
- // into this row after an owner rotates the shared secret.
929
+ // into this row after an owner rotates the shared secret or edits the
930
+ // declaration. Compared as values, not as serialised text: the stored side
931
+ // has been through jsonb, which does not keep key order, and a declared
932
+ // sign-in's side is built here.
470
933
  let existingTokens;
471
- if (existingUserRow?.kind === 'oauth' &&
472
- JSON.stringify(existingUserRow.oauthMeta) === JSON.stringify(shared.oauthMeta)) {
934
+ if (existingUserRow?.kind === 'oauth' && isDeepStrictEqual(existingUserRow.oauthMeta, oauthMeta)) {
473
935
  try {
474
936
  existingTokens = this.readBlob(existingUserRow.valueEncrypted).tokens;
475
937
  }
@@ -487,9 +949,13 @@ export class DbSecretsVaultService {
487
949
  // that echoes no `scope` is read as granting exactly this (RFC 6749 §5.1).
488
950
  const requestedScopes = input.scopes && input.scopes.length ? input.scopes : meta.scopes;
489
951
  const pendingScopes = requestedScopes && requestedScopes.length ? requestedScopes.join(' ') : undefined;
490
- // Provision (or reset) the caller's own oauth row for this key from the shared
491
- // provider meta + secret, preserving any existing tokens. Keyed `<manual>_<VAR>`
492
- // so `resolve` (scope 'user') returns the token once sign-in completes.
952
+ // Provision (or reset) the caller's own oauth row for this key from the provider
953
+ // configuration this start uses + the shared secret, preserving any existing
954
+ // tokens. The row records that configuration because `completeOAuth` and every
955
+ // later refresh of this grant read it from HERE: the exchange goes to the token
956
+ // endpoint, under the client id, that the consent was given for. Keyed
957
+ // `<manual>_<VAR>` so `resolve` (scope 'user') returns the token once sign-in
958
+ // completes.
493
959
  const blob = {
494
960
  clientSecret: sharedBlob.clientSecret,
495
961
  tokens: existingTokens,
@@ -497,17 +963,22 @@ export class DbSecretsVaultService {
497
963
  pendingScopes,
498
964
  };
499
965
  const valueEncrypted = this.crypto().encrypt(JSON.stringify(blob));
500
- const [row] = await this.db
966
+ // A managed sign-in on the caller's row (an owner pinned their own app
967
+ // after the person connected through the broker) is replaced by this
968
+ // start: its tokens are told to the provider first, and the write is
969
+ // guarded on what the revoke read. Tokens carried over above were minted
970
+ // under THIS configuration, never a managed one.
971
+ const row = await this.revokedReplacement(and(eq(secrets.userId, userId), eq(secrets.key, key)), (guard) => this.db
501
972
  .insert(secrets)
502
- .values({ userId, key, kind: 'oauth', label: shared.label, valueEncrypted, oauthMeta: shared.oauthMeta })
973
+ .values({ userId, key, kind: 'oauth', label: shared.label, valueEncrypted, oauthMeta })
503
974
  .onConflictDoUpdate({
504
975
  target: [secrets.userId, secrets.key],
505
- set: { kind: 'oauth', valueEncrypted, oauthMeta: shared.oauthMeta, updatedAt: new Date() },
976
+ set: { kind: 'oauth', valueEncrypted, oauthMeta, updatedAt: new Date() },
977
+ ...(guard ? { setWhere: guard } : {}),
506
978
  })
507
- .returning();
508
- // Build the consent URL exactly as beginOAuth does, from the stored meta —
509
- // EXCEPT the requested scopes (above). Client id, addresses, and secret
510
- // stay owner-pinned.
979
+ .returning());
980
+ // Build the consent URL exactly as beginOAuth does, from the configuration
981
+ // this start uses, with the requested scopes chosen above.
511
982
  const url = new URL(meta.authorizationUrl);
512
983
  url.searchParams.set('response_type', 'code');
513
984
  url.searchParams.set('client_id', meta.clientId);
@@ -583,10 +1054,16 @@ export class DbSecretsVaultService {
583
1054
  return result.accessToken;
584
1055
  }
585
1056
  async forceRefresh(userId, key) {
1057
+ // The caller's own row, or — for a variable the manual declares shared
1058
+ // (a managed workspace connection) — the one shared row every caller
1059
+ // resolves. Same tier rule as `resolve`, so a 401 against a shared
1060
+ // token repairs the row the call actually used.
1061
+ const scope = (await this.scopeOf?.(key)) ?? 'admin';
1062
+ const scopeFilter = scope === 'user' ? eq(secrets.userId, userId) : isNull(secrets.userId);
586
1063
  const [row] = await this.db
587
1064
  .select()
588
1065
  .from(secrets)
589
- .where(and(eq(secrets.userId, userId), eq(secrets.key, key)))
1066
+ .where(and(scopeFilter, eq(secrets.key, key)))
590
1067
  .limit(1);
591
1068
  if (!row || row.kind !== 'oauth')
592
1069
  return 'skipped';
@@ -627,18 +1104,38 @@ export class DbSecretsVaultService {
627
1104
  return this.wipeTokens(row, blob);
628
1105
  }
629
1106
  const meta = this.readMeta(row.oauthMeta);
630
- const body = new URLSearchParams({
631
- grant_type: 'refresh_token',
632
- refresh_token: tokens.refresh_token,
633
- client_id: meta.clientId,
634
- });
635
- if (blob.clientSecret)
636
- body.set('client_secret', blob.clientSecret);
637
- if (meta.resource)
638
- body.set('resource', meta.resource);
639
1107
  let refreshed;
640
1108
  try {
641
- refreshed = await this.tokenRequest(meta.tokenUrl, body);
1109
+ if (meta.managed) {
1110
+ // A managed sign-in renews through the broker, which holds the
1111
+ // client secret this deployment never sees. No broker wired is an
1112
+ // outage from here, not a dead grant: the stale token is served and
1113
+ // the next call asks again.
1114
+ if (!this.managedRefresher)
1115
+ return { outcome: 'transient' };
1116
+ // Bounded like a revocation: a broker that never answers is an outage
1117
+ // (the stale token is served) and must not hold every resolution.
1118
+ const delivered = await bounded(this.managedRefresher.refresh(meta.managed, tokens.refresh_token), REFRESH_TIMEOUT_MS, 'the broker refresh');
1119
+ refreshed = {
1120
+ access_token: delivered.access_token,
1121
+ refresh_token: delivered.refresh_token,
1122
+ expires_at: delivered.expires_at,
1123
+ token_type: delivered.token_type,
1124
+ scope: delivered.scope,
1125
+ };
1126
+ }
1127
+ else {
1128
+ const body = new URLSearchParams({
1129
+ grant_type: 'refresh_token',
1130
+ refresh_token: tokens.refresh_token,
1131
+ client_id: meta.clientId,
1132
+ });
1133
+ if (blob.clientSecret)
1134
+ body.set('client_secret', blob.clientSecret);
1135
+ if (meta.resource)
1136
+ body.set('resource', meta.resource);
1137
+ refreshed = await this.tokenRequest(meta.tokenUrl, body);
1138
+ }
642
1139
  }
643
1140
  catch (err) {
644
1141
  // A DEFINITIVE provider rejection (400/401 — invalid_grant and friends)
@@ -649,7 +1146,23 @@ export class DbSecretsVaultService {
649
1146
  // of letting calls fail opaquely at the provider forever.
650
1147
  if (err instanceof SecretOAuthError &&
651
1148
  (err.providerStatus === 400 || err.providerStatus === 401)) {
652
- return this.wipeTokens(row, blob);
1149
+ // The only trace of a sign-in that silently turns into "not connected":
1150
+ // say which one, the provider's reason, and what the guarded wipe did —
1151
+ // a concurrent refresh may have rotated the tokens first, in which case
1152
+ // nothing was wiped and the rotated grant is served.
1153
+ const result = await this.wipeTokens(row, blob);
1154
+ const did = result.wiped
1155
+ ? 'sign-in wiped'
1156
+ : result.outcome === 'refreshed'
1157
+ ? 'the row had been rotated by a concurrent refresh meanwhile; the rotated sign-in is kept'
1158
+ : 'the row had been removed or replaced meanwhile; nothing wiped';
1159
+ // A managed row's refusal came from the refresher, whose words are its
1160
+ // own (a port may put the refresh token it was handed in them): only
1161
+ // the status it reported is logged. The vault's own token request
1162
+ // writes fixed sentences, which are safe to repeat.
1163
+ const why = meta.managed ? `the broker refused it (HTTP ${err.providerStatus ?? 'unknown'})` : err.message;
1164
+ log.warn(`refresh refused for "${row.key}", ${did}: ${why}`);
1165
+ return result;
653
1166
  }
654
1167
  return { outcome: 'transient' };
655
1168
  }
@@ -662,7 +1175,10 @@ export class DbSecretsVaultService {
662
1175
  // value, empty included, is the provider's word.
663
1176
  if (refreshed.scope === undefined)
664
1177
  refreshed.scope = tokens.scope;
665
- const next = { clientSecret: blob.clientSecret, tokens: refreshed };
1178
+ // A sign-in in flight on this row (a managed reconnect waiting for its
1179
+ // handoff, a consent waiting for its exchange) survives the refresh: the
1180
+ // nonce or verifier it minted is what that handoff must match.
1181
+ const next = { clientSecret: blob.clientSecret, tokens: refreshed, ...this.inFlightOf(blob) };
666
1182
  // Optimistic concurrency: only persist if the stored ciphertext is unchanged,
667
1183
  // so a concurrent refresh on the same row doesn't clobber (Microsoft pattern).
668
1184
  const stored = await this.db
@@ -708,9 +1224,23 @@ export class DbSecretsVaultService {
708
1224
  return undefined;
709
1225
  }
710
1226
  }
711
- /** Drop a dead grant's token set, keeping the client secret. See {@link refreshRow}. */
1227
+ /**
1228
+ * What a row holds for a sign-in IN FLIGHT: the one-time nonce a managed
1229
+ * handoff must echo, the PKCE verifier and requested scopes a code exchange
1230
+ * must present. A refresh or a wipe rewrites the row's tokens, never these:
1231
+ * dropping them would refuse the handoff or the exchange that is about to
1232
+ * land, for a reason the person could not see.
1233
+ */
1234
+ inFlightOf(blob) {
1235
+ return {
1236
+ ...(blob.pendingNonce ? { pendingNonce: blob.pendingNonce } : {}),
1237
+ ...(blob.pendingVerifier ? { pendingVerifier: blob.pendingVerifier } : {}),
1238
+ ...(blob.pendingScopes ? { pendingScopes: blob.pendingScopes } : {}),
1239
+ };
1240
+ }
1241
+ /** Drop a dead grant's token set, keeping the client secret and any sign-in in flight. See {@link refreshRow}. */
712
1242
  async wipeTokens(row, blob) {
713
- const next = { clientSecret: blob.clientSecret };
1243
+ const next = { clientSecret: blob.clientSecret, ...this.inFlightOf(blob) };
714
1244
  // Guarded on the ciphertext we read, so a concurrent refresh that
715
1245
  // already persisted ROTATED tokens can't be wiped by our stale
716
1246
  // failure. When that guard trips (0 rows), the failure was against a
@@ -722,13 +1252,17 @@ export class DbSecretsVaultService {
722
1252
  .where(and(eq(secrets.id, row.id), eq(secrets.valueEncrypted, row.valueEncrypted)))
723
1253
  .returning({ id: secrets.id });
724
1254
  if (Array.isArray(wiped) && wiped.length === 0) {
1255
+ // `wiped: false` either way: this write matched nothing, and the caller
1256
+ // reports what it did, not what it set out to do.
725
1257
  const fresh = await this.currentAccessToken(row.id);
726
- return fresh ? { outcome: 'refreshed', accessToken: fresh } : { outcome: 'rejected', accessToken: null };
1258
+ return fresh
1259
+ ? { outcome: 'refreshed', accessToken: fresh, wiped: false }
1260
+ : { outcome: 'rejected', accessToken: null, wiped: false };
727
1261
  }
728
1262
  // The sign-in just became not-connected — let credential-validity caches
729
1263
  // (pooled downstream connections dialed with the dead token) drop it now.
730
1264
  this.notifyMutation(row.userId);
731
- return { outcome: 'rejected', accessToken: null };
1265
+ return { outcome: 'rejected', accessToken: null, wiped: true };
732
1266
  }
733
1267
  async putSharedOAuthProvider(input) {
734
1268
  const key = this.requireKey(input.key);
@@ -739,7 +1273,7 @@ export class DbSecretsVaultService {
739
1273
  const blob = { clientSecret: provider.clientSecret };
740
1274
  const valueEncrypted = this.crypto().encrypt(JSON.stringify(blob));
741
1275
  const oauthMeta = this.publicProviderMeta(provider);
742
- await this.db
1276
+ await this.revokedReplacement(and(isNull(secrets.userId), eq(secrets.key, key)), (guard) => this.db
743
1277
  .insert(secrets)
744
1278
  .values({ userId: null, key, kind: 'oauth', label, valueEncrypted, oauthMeta })
745
1279
  // Preserve an existing row's label on re-upsert (matches
@@ -749,7 +1283,9 @@ export class DbSecretsVaultService {
749
1283
  target: secrets.key,
750
1284
  targetWhere: isNull(secrets.userId),
751
1285
  set: { kind: 'oauth', valueEncrypted, oauthMeta, updatedAt: new Date() },
752
- });
1286
+ ...(guard ? { setWhere: guard } : {}),
1287
+ })
1288
+ .returning({ id: secrets.id }));
753
1289
  this.notifyMutation(null);
754
1290
  }
755
1291
  async getSharedOAuthProvider(key) {
@@ -797,7 +1333,15 @@ export class DbSecretsVaultService {
797
1333
  throw new SecretOAuthError(msg);
798
1334
  }
799
1335
  if (!res.ok) {
800
- throw new SecretOAuthError(`Token endpoint returned HTTP ${res.status}`, res.status);
1336
+ // The provider's error CODE rides in the message: the callback route and
1337
+ // the refresh path log that message, and it is the one line that says
1338
+ // WHY a sign-in failed (a client the provider does not accept, a refused
1339
+ // authorization code). Best effort: the STATUS is the contract (the
1340
+ // refresh path reads 400/401 off it to retire a dead grant), and a body
1341
+ // that fails to read must not turn a definitive refusal into an
1342
+ // unclassified error.
1343
+ const code = tokenEndpointErrorCode(await readCapped(res, TOKEN_ERROR_BODY_MAX_BYTES).catch(() => ''));
1344
+ throw new SecretOAuthError(describeTokenEndpointRefusal(res.status, code), res.status);
801
1345
  }
802
1346
  const json = (await res.json());
803
1347
  const accessToken = typeof json.access_token === 'string' ? json.access_token : '';
@@ -835,13 +1379,17 @@ export class DbSecretsVaultService {
835
1379
  const authorizationUrl = typeof meta.authorizationUrl === 'string' ? meta.authorizationUrl : '';
836
1380
  const tokenUrl = typeof meta.tokenUrl === 'string' ? meta.tokenUrl : '';
837
1381
  const clientId = typeof meta.clientId === 'string' ? meta.clientId : '';
838
- if (!authorizationUrl || !tokenUrl || !clientId) {
1382
+ const managed = this.managedRefOf(meta) ?? undefined;
1383
+ // A managed row has no client id of its own (the broker's app is the
1384
+ // client); everything else must name one, or no flow can run from it.
1385
+ if (!authorizationUrl || !tokenUrl || (!clientId && !managed)) {
839
1386
  throw new SecretOAuthError('OAuth secret is missing provider configuration');
840
1387
  }
841
1388
  return {
842
1389
  authorizationUrl,
843
1390
  tokenUrl,
844
1391
  clientId,
1392
+ managed,
845
1393
  scopes: Array.isArray(meta.scopes) ? meta.scopes.map(String) : undefined,
846
1394
  authParams: meta.authParams && typeof meta.authParams === 'object'
847
1395
  ? meta.authParams
@@ -861,16 +1409,27 @@ export class DbSecretsVaultService {
861
1409
  ...(p.pkce ? { pkce: true } : {}),
862
1410
  ...(p.publicClient ? { publicClient: true } : {}),
863
1411
  ...(p.resource ? { resource: p.resource } : {}),
1412
+ ...(p.managed ? { managed: { connectorId: p.managed.connectorId, scopeSet: p.managed.scopeSet } } : {}),
1413
+ ...(p.account ? { account: p.account } : {}),
864
1414
  };
865
1415
  }
866
- requireProvider(p) {
1416
+ /**
1417
+ * @param opts.managed Accept a MANAGED provider (no client id of its own,
1418
+ * a connector reference instead). Only the managed sign-in paths pass it:
1419
+ * everywhere else a provider needs a client id, and a `managed` field a
1420
+ * caller slipped in is dropped rather than making the row one.
1421
+ */
1422
+ requireProvider(p, opts = {}) {
867
1423
  if (!p || typeof p !== 'object')
868
1424
  throw new InvalidSecretError('provider is required');
869
1425
  const cfg = p;
870
1426
  const authorizationUrl = this.requireUrl(cfg.authorizationUrl, 'authorizationUrl');
871
1427
  const tokenUrl = this.requireUrl(cfg.tokenUrl, 'tokenUrl');
872
- const clientId = typeof cfg.clientId === 'string' ? cfg.clientId.trim() : '';
873
- if (!clientId)
1428
+ const managed = opts.managed ? (this.managedRefOf(cfg) ?? undefined) : undefined;
1429
+ // A managed sign-in has no client of its own: whatever id a caller sends
1430
+ // beside the connector reference is not stored, so no row can claim one.
1431
+ const clientId = managed ? '' : typeof cfg.clientId === 'string' ? cfg.clientId.trim() : '';
1432
+ if (!clientId && !managed)
874
1433
  throw new InvalidSecretError('clientId is required');
875
1434
  const scopes = Array.isArray(cfg.scopes) ? cfg.scopes.map(String) : undefined;
876
1435
  const authParams = cfg.authParams && typeof cfg.authParams === 'object'
@@ -890,6 +1449,8 @@ export class DbSecretsVaultService {
890
1449
  resource: typeof cfg.resource === 'string' && cfg.resource.trim()
891
1450
  ? this.requireUrl(cfg.resource, 'resource')
892
1451
  : undefined,
1452
+ managed,
1453
+ account: typeof cfg.account === 'string' && cfg.account.trim() ? cfg.account.trim().slice(0, MAX_LABEL_LEN) : undefined,
893
1454
  };
894
1455
  }
895
1456
  requireUrl(value, field) {