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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (851) hide show
  1. package/agent-guide/directory-structure.md +1 -1
  2. package/agent-guide/onboarding.md +111 -0
  3. package/agent-guide/start-here.md +1 -0
  4. package/agent-guide/tool-manuals.md +1 -0
  5. package/dist/core/catalog-cache-invalidation.d.ts +2 -2
  6. package/dist/core/catalog-cache-invalidation.d.ts.map +1 -1
  7. package/dist/core/compose-embed.d.ts +2 -2
  8. package/dist/core/compose-embed.d.ts.map +1 -1
  9. package/dist/core/compose-embed.js +1 -1
  10. package/dist/core/compose-embed.js.map +1 -1
  11. package/dist/core/core-ports.d.ts +19 -4
  12. package/dist/core/core-ports.d.ts.map +1 -1
  13. package/dist/core/core-ports.js.map +1 -1
  14. package/dist/core/create-core-server.d.ts.map +1 -1
  15. package/dist/core/create-core-server.js +26 -24
  16. package/dist/core/create-core-server.js.map +1 -1
  17. package/dist/core/create-core-services.d.ts +19 -13
  18. package/dist/core/create-core-services.d.ts.map +1 -1
  19. package/dist/core/create-core-services.js +72 -57
  20. package/dist/core/create-core-services.js.map +1 -1
  21. package/dist/core/lifecycle.d.ts +4 -4
  22. package/dist/core/lifecycle.d.ts.map +1 -1
  23. package/dist/core/lifecycle.js +5 -5
  24. package/dist/core/lifecycle.js.map +1 -1
  25. package/dist/core-config.d.ts +2 -2
  26. package/dist/core-config.js +3 -3
  27. package/dist/core-config.js.map +1 -1
  28. package/dist/index.d.ts +4 -4
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +3 -3
  31. package/dist/index.js.map +1 -1
  32. package/dist/modules/access/access-control.service.js +1 -1
  33. package/dist/modules/access/access-control.service.js.map +1 -1
  34. package/dist/modules/access/access-declarations.d.ts +2 -2
  35. package/dist/modules/access/access-declarations.d.ts.map +1 -1
  36. package/dist/modules/access/access-declarations.js +2 -2
  37. package/dist/modules/access/access-declarations.js.map +1 -1
  38. package/dist/modules/access/access-mutation.service.d.ts +2 -2
  39. package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
  40. package/dist/modules/access/access-mutation.service.js +2 -2
  41. package/dist/modules/access/access-mutation.service.js.map +1 -1
  42. package/dist/modules/access/access-requests.routes.d.ts +2 -2
  43. package/dist/modules/access/access-requests.routes.d.ts.map +1 -1
  44. package/dist/modules/access/access-requests.routes.js +5 -5
  45. package/dist/modules/access/access-requests.routes.js.map +1 -1
  46. package/dist/modules/access/access-requests.service.d.ts +2 -2
  47. package/dist/modules/access/access-requests.service.d.ts.map +1 -1
  48. package/dist/modules/access/access-requests.service.js +11 -11
  49. package/dist/modules/access/access-requests.service.js.map +1 -1
  50. package/dist/modules/access/access.routes.d.ts +2 -2
  51. package/dist/modules/access/access.routes.d.ts.map +1 -1
  52. package/dist/modules/access/access.routes.js +9 -9
  53. package/dist/modules/access/access.routes.js.map +1 -1
  54. package/dist/modules/access/admin-locked-commit.d.ts +5 -5
  55. package/dist/modules/access/admin-locked-commit.d.ts.map +1 -1
  56. package/dist/modules/access/admin-locked-commit.js +7 -7
  57. package/dist/modules/access/admin-locked-commit.js.map +1 -1
  58. package/dist/modules/access/admin-route-helpers.d.ts +1 -1
  59. package/dist/modules/access/admin-route-helpers.js +5 -5
  60. package/dist/modules/access/admin-route-helpers.js.map +1 -1
  61. package/dist/modules/access/change-read-gate.d.ts +1 -1
  62. package/dist/modules/access/change-read-gate.js +1 -1
  63. package/dist/modules/access/groups-admin.service.d.ts +7 -7
  64. package/dist/modules/access/groups-admin.service.d.ts.map +1 -1
  65. package/dist/modules/access/groups-admin.service.js +6 -6
  66. package/dist/modules/access/groups-admin.service.js.map +1 -1
  67. package/dist/modules/access/reference-scan.d.ts +1 -1
  68. package/dist/modules/access/roles-admin.service.d.ts +7 -7
  69. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  70. package/dist/modules/access/roles-admin.service.js +8 -8
  71. package/dist/modules/access/roles-admin.service.js.map +1 -1
  72. package/dist/modules/access/synced-groups-committer.d.ts +4 -4
  73. package/dist/modules/access/synced-groups-committer.d.ts.map +1 -1
  74. package/dist/modules/access/synced-groups-committer.js +7 -7
  75. package/dist/modules/access/synced-groups-committer.js.map +1 -1
  76. package/dist/modules/access/user-access-removal.service.d.ts +6 -6
  77. package/dist/modules/access/user-access-removal.service.d.ts.map +1 -1
  78. package/dist/modules/access/user-access-removal.service.js +4 -4
  79. package/dist/modules/access/user-access-removal.service.js.map +1 -1
  80. package/dist/modules/access-model/access-errors.d.ts +4 -4
  81. package/dist/modules/access-model/access-errors.d.ts.map +1 -1
  82. package/dist/modules/access-model/access-errors.js +4 -4
  83. package/dist/modules/access-model/access-errors.js.map +1 -1
  84. package/dist/modules/access-model/access-grammar.d.ts +1 -1
  85. package/dist/modules/access-model/access-grammar.js +4 -4
  86. package/dist/modules/access-model/change-gate.d.ts +1 -1
  87. package/dist/modules/access-model/change-gate.js +1 -1
  88. package/dist/modules/access-model/roles-yaml-guard.d.ts +4 -4
  89. package/dist/modules/access-model/roles-yaml-guard.d.ts.map +1 -1
  90. package/dist/modules/access-model/roles-yaml-guard.js +4 -4
  91. package/dist/modules/access-model/roles-yaml-guard.js.map +1 -1
  92. package/dist/modules/agent-guide/agent-guide.d.ts +19 -14
  93. package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
  94. package/dist/modules/agent-guide/agent-guide.js +42 -18
  95. package/dist/modules/agent-guide/agent-guide.js.map +1 -1
  96. package/dist/modules/agent-guide/guide-budget.d.ts +5 -2
  97. package/dist/modules/agent-guide/guide-budget.d.ts.map +1 -1
  98. package/dist/modules/agent-guide/guide-budget.js +6 -2
  99. package/dist/modules/agent-guide/guide-budget.js.map +1 -1
  100. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +12 -5
  101. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -1
  102. package/dist/modules/agent-instructions/agent-instructions.routes.js +13 -6
  103. package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -1
  104. package/dist/modules/agent-instructions/compose.d.ts +15 -8
  105. package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
  106. package/dist/modules/agent-instructions/compose.js +24 -10
  107. package/dist/modules/agent-instructions/compose.js.map +1 -1
  108. package/dist/modules/agent-instructions/shared-file-rules.d.ts +14 -6
  109. package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
  110. package/dist/modules/agent-instructions/shared-file-rules.js +51 -11
  111. package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
  112. package/dist/modules/auth/account-erasure.service.d.ts +36 -7
  113. package/dist/modules/auth/account-erasure.service.d.ts.map +1 -1
  114. package/dist/modules/auth/account-erasure.service.js +30 -10
  115. package/dist/modules/auth/account-erasure.service.js.map +1 -1
  116. package/dist/modules/auth/account-hooks.d.ts +20 -0
  117. package/dist/modules/auth/account-hooks.d.ts.map +1 -1
  118. package/dist/modules/auth/account-hooks.js +10 -0
  119. package/dist/modules/auth/account-hooks.js.map +1 -1
  120. package/dist/modules/auth/account-switch-lock.js +2 -2
  121. package/dist/modules/auth/account-switch-lock.js.map +1 -1
  122. package/dist/modules/auth/account.routes.d.ts +7 -2
  123. package/dist/modules/auth/account.routes.d.ts.map +1 -1
  124. package/dist/modules/auth/account.routes.js +23 -4
  125. package/dist/modules/auth/account.routes.js.map +1 -1
  126. package/dist/modules/auth/auth.service.d.ts +1 -1
  127. package/dist/modules/auth/auth.service.js +4 -4
  128. package/dist/modules/auth/auth.service.js.map +1 -1
  129. package/dist/modules/auth/index.d.ts +1 -0
  130. package/dist/modules/auth/index.d.ts.map +1 -1
  131. package/dist/modules/database/connection.d.ts.map +1 -1
  132. package/dist/modules/database/connection.js +11 -0
  133. package/dist/modules/database/connection.js.map +1 -1
  134. package/dist/modules/database/core-schema.js +2 -2
  135. package/dist/modules/database/core-schema.js.map +1 -1
  136. package/dist/modules/diff/diff.routes.d.ts +3 -3
  137. package/dist/modules/diff/diff.routes.d.ts.map +1 -1
  138. package/dist/modules/diff/diff.routes.js +7 -7
  139. package/dist/modules/diff/diff.routes.js.map +1 -1
  140. package/dist/modules/embed/embed.routes.js +1 -1
  141. package/dist/modules/embed/embed.routes.js.map +1 -1
  142. package/dist/modules/embed/embed.service.d.ts +4 -4
  143. package/dist/modules/embed/embed.service.d.ts.map +1 -1
  144. package/dist/modules/embed/embed.service.js +18 -18
  145. package/dist/modules/embed/embed.service.js.map +1 -1
  146. package/dist/modules/kb-fs/branch-name.js +8 -8
  147. package/dist/modules/kb-fs/branch-name.js.map +1 -1
  148. package/dist/modules/kb-fs/file-change-notifier.d.ts +51 -26
  149. package/dist/modules/kb-fs/file-change-notifier.d.ts.map +1 -1
  150. package/dist/modules/kb-fs/file-change-notifier.js +6 -8
  151. package/dist/modules/kb-fs/file-change-notifier.js.map +1 -1
  152. package/dist/modules/kb-fs/locking-filesystem.d.ts +9 -16
  153. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
  154. package/dist/modules/kb-fs/locking-filesystem.js +31 -39
  155. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
  156. package/dist/modules/kb-fs/repo-path.js +5 -5
  157. package/dist/modules/kb-fs/repo-path.js.map +1 -1
  158. package/dist/modules/kb-fs/tmp-ref.d.ts +2 -2
  159. package/dist/modules/kb-fs/tmp-ref.d.ts.map +1 -1
  160. package/dist/modules/kb-fs/tmp-ref.js +2 -2
  161. package/dist/modules/kb-fs/tmp-ref.js.map +1 -1
  162. package/dist/modules/kb-sync/index.d.ts +1 -1
  163. package/dist/modules/kb-sync/index.d.ts.map +1 -1
  164. package/dist/modules/kb-sync/kb-sync.interface.d.ts +3 -3
  165. package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -1
  166. package/dist/modules/kb-sync/kb-sync.service.d.ts +3 -3
  167. package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
  168. package/dist/modules/kb-sync/kb-sync.service.js +10 -10
  169. package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
  170. package/dist/modules/mcp/mcp.service.d.ts +11 -4
  171. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  172. package/dist/modules/mcp/mcp.service.js +75 -19
  173. package/dist/modules/mcp/mcp.service.js.map +1 -1
  174. package/dist/modules/onboarding/onboarding.contract.d.ts +1 -1
  175. package/dist/modules/onboarding/onboarding.routes.js +2 -2
  176. package/dist/modules/onboarding/onboarding.routes.js.map +1 -1
  177. package/dist/modules/onboarding/starter-pack.service.d.ts +5 -7
  178. package/dist/modules/onboarding/starter-pack.service.d.ts.map +1 -1
  179. package/dist/modules/onboarding/starter-pack.service.js +4 -4
  180. package/dist/modules/onboarding/starter-pack.service.js.map +1 -1
  181. package/dist/modules/plugins/join-request-jobs.service.d.ts +2 -2
  182. package/dist/modules/plugins/join-request-jobs.service.d.ts.map +1 -1
  183. package/dist/modules/plugins/join-request-jobs.service.js +7 -7
  184. package/dist/modules/plugins/join-request-jobs.service.js.map +1 -1
  185. package/dist/modules/plugins/join-requests.service.d.ts +3 -3
  186. package/dist/modules/plugins/join-requests.service.d.ts.map +1 -1
  187. package/dist/modules/plugins/join-requests.service.js +5 -5
  188. package/dist/modules/plugins/join-requests.service.js.map +1 -1
  189. package/dist/modules/plugins/plugin-rename.service.js +1 -1
  190. package/dist/modules/plugins/plugin-rename.service.js.map +1 -1
  191. package/dist/modules/plugins/plugins.routes.d.ts +2 -2
  192. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  193. package/dist/modules/plugins/plugins.routes.js +8 -8
  194. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  195. package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +77 -2
  196. package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
  197. package/dist/modules/secrets-vault/db-secrets-vault.service.js +479 -53
  198. package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
  199. package/dist/modules/secrets-vault/index.d.ts +2 -1
  200. package/dist/modules/secrets-vault/index.d.ts.map +1 -1
  201. package/dist/modules/secrets-vault/index.js +1 -0
  202. package/dist/modules/secrets-vault/index.js.map +1 -1
  203. package/dist/modules/secrets-vault/managed-sign-in-erasure.d.ts +36 -0
  204. package/dist/modules/secrets-vault/managed-sign-in-erasure.d.ts.map +1 -0
  205. package/dist/modules/secrets-vault/managed-sign-in-erasure.js +37 -0
  206. package/dist/modules/secrets-vault/managed-sign-in-erasure.js.map +1 -0
  207. package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +152 -0
  208. package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
  209. package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
  210. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +7 -1
  211. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
  212. package/dist/modules/secrets-vault/secrets-vault.routes.js +125 -10
  213. package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
  214. package/dist/modules/settings/connection-check.js +1 -1
  215. package/dist/modules/settings/connection-check.js.map +1 -1
  216. package/dist/modules/settings/deployment-settings.service.js +1 -1
  217. package/dist/modules/settings/git-root-folders.js +1 -1
  218. package/dist/modules/settings/git-root-folders.js.map +1 -1
  219. package/dist/modules/settings/setup.routes.d.ts +1 -1
  220. package/dist/modules/settings/setup.routes.js +1 -1
  221. package/dist/modules/skills/pending-skills.service.d.ts +3 -3
  222. package/dist/modules/skills/pending-skills.service.d.ts.map +1 -1
  223. package/dist/modules/skills/pending-skills.service.js +4 -4
  224. package/dist/modules/skills/pending-skills.service.js.map +1 -1
  225. package/dist/modules/skills/skill-access-requests.routes.d.ts +2 -2
  226. package/dist/modules/skills/skill-access-requests.routes.d.ts.map +1 -1
  227. package/dist/modules/skills/skill-access-requests.routes.js +5 -5
  228. package/dist/modules/skills/skill-access-requests.routes.js.map +1 -1
  229. package/dist/modules/tool-helpers/tool-context.d.ts +4 -4
  230. package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
  231. package/dist/modules/tool-helpers/tool-context.js +2 -2
  232. package/dist/modules/tool-helpers/tool-context.js.map +1 -1
  233. package/dist/modules/tool-helpers/tool-handler.js +2 -2
  234. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  235. package/dist/modules/tool-helpers/tool.contract.d.ts +4 -4
  236. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  237. package/dist/modules/tool-manuals/mcp-server-edit.service.js +1 -1
  238. package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -1
  239. package/dist/modules/tool-manuals/pending-tools.service.d.ts +3 -3
  240. package/dist/modules/tool-manuals/pending-tools.service.d.ts.map +1 -1
  241. package/dist/modules/tool-manuals/pending-tools.service.js +4 -4
  242. package/dist/modules/tool-manuals/pending-tools.service.js.map +1 -1
  243. package/dist/modules/tool-manuals/tool-delete.service.js +1 -1
  244. package/dist/modules/tool-manuals/tool-delete.service.js.map +1 -1
  245. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +43 -3
  246. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  247. package/dist/modules/tool-manuals/tool-manuals.service.d.ts +6 -0
  248. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  249. package/dist/modules/tool-manuals/tool-manuals.service.js +51 -5
  250. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  251. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +3 -0
  252. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  253. package/dist/modules/tool-manuals/tool-manuals.tools.js +36 -15
  254. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  255. package/dist/modules/version-control/agent-tools/change-request-read-shape.d.ts.map +1 -0
  256. package/dist/modules/version-control/agent-tools/change-request-read-shape.js.map +1 -0
  257. package/dist/modules/version-control/agent-tools/change-request-read.tools.d.ts.map +1 -0
  258. package/dist/modules/{workflow → version-control}/agent-tools/change-request-read.tools.js +2 -2
  259. package/dist/modules/version-control/agent-tools/change-request-read.tools.js.map +1 -0
  260. package/dist/modules/version-control/agent-tools/change-request-summary.d.ts.map +1 -0
  261. package/dist/modules/version-control/agent-tools/change-request-summary.js.map +1 -0
  262. package/dist/modules/{workflow/agent-tools/workflow.tools.d.ts → version-control/agent-tools/version-control.tools.d.ts} +4 -4
  263. package/dist/modules/version-control/agent-tools/version-control.tools.d.ts.map +1 -0
  264. package/dist/modules/{workflow/agent-tools/workflow.tools.js → version-control/agent-tools/version-control.tools.js} +31 -31
  265. package/dist/modules/version-control/agent-tools/version-control.tools.js.map +1 -0
  266. package/dist/modules/{workflow → version-control}/change-request-hooks.d.ts +1 -1
  267. package/dist/modules/version-control/change-request-hooks.d.ts.map +1 -0
  268. package/dist/modules/version-control/change-request-hooks.js.map +1 -0
  269. package/dist/modules/version-control/change-request-review/approval-lock.d.ts.map +1 -0
  270. package/dist/modules/version-control/change-request-review/approval-lock.js.map +1 -0
  271. package/dist/modules/{workflow/review-workflow/review-workflow.interface.d.ts → version-control/change-request-review/change-request-review.interface.d.ts} +2 -2
  272. package/dist/modules/version-control/change-request-review/change-request-review.interface.d.ts.map +1 -0
  273. package/dist/modules/version-control/change-request-review/change-request-review.interface.js +2 -0
  274. package/dist/modules/version-control/change-request-review/change-request-review.interface.js.map +1 -0
  275. package/dist/modules/{workflow/review-workflow/review-workflow.service.d.ts → version-control/change-request-review/change-request-review.service.d.ts} +3 -3
  276. package/dist/modules/version-control/change-request-review/change-request-review.service.d.ts.map +1 -0
  277. package/dist/modules/{workflow/review-workflow/review-workflow.service.js → version-control/change-request-review/change-request-review.service.js} +26 -26
  278. package/dist/modules/version-control/change-request-review/change-request-review.service.js.map +1 -0
  279. package/dist/modules/{workflow → version-control}/event-bus.d.ts +8 -8
  280. package/dist/modules/version-control/event-bus.d.ts.map +1 -0
  281. package/dist/modules/{workflow → version-control}/event-bus.js +4 -4
  282. package/dist/modules/version-control/event-bus.js.map +1 -0
  283. package/dist/modules/{workflow → version-control}/events-delivery.d.ts +4 -4
  284. package/dist/modules/version-control/events-delivery.d.ts.map +1 -0
  285. package/dist/modules/version-control/events-delivery.js.map +1 -0
  286. package/dist/modules/{workflow → version-control}/events.routes.d.ts +4 -4
  287. package/dist/modules/version-control/events.routes.d.ts.map +1 -0
  288. package/dist/modules/version-control/events.routes.js.map +1 -0
  289. package/dist/modules/{workflow → version-control}/file-lock.service.d.ts +2 -2
  290. package/dist/modules/version-control/file-lock.service.d.ts.map +1 -0
  291. package/dist/modules/{workflow → version-control}/file-lock.service.js +6 -6
  292. package/dist/modules/version-control/file-lock.service.js.map +1 -0
  293. package/dist/modules/version-control/git/change-request-link.d.ts.map +1 -0
  294. package/dist/modules/version-control/git/change-request-link.js.map +1 -0
  295. package/dist/modules/version-control/git/git-version.d.ts.map +1 -0
  296. package/dist/modules/version-control/git/git-version.js.map +1 -0
  297. package/dist/modules/{workflow → version-control}/git/git.service.d.ts +26 -8
  298. package/dist/modules/version-control/git/git.service.d.ts.map +1 -0
  299. package/dist/modules/{workflow → version-control}/git/git.service.js +106 -55
  300. package/dist/modules/version-control/git/git.service.js.map +1 -0
  301. package/dist/modules/version-control/git/merge-commit.d.ts.map +1 -0
  302. package/dist/modules/version-control/git/merge-commit.js.map +1 -0
  303. package/dist/modules/version-control/git/node-git-runner.d.ts.map +1 -0
  304. package/dist/modules/{workflow → version-control}/git/node-git-runner.js +1 -1
  305. package/dist/modules/version-control/git/node-git-runner.js.map +1 -0
  306. package/dist/modules/{workflow → version-control}/git/pull-request.service.d.ts +5 -5
  307. package/dist/modules/version-control/git/pull-request.service.d.ts.map +1 -0
  308. package/dist/modules/{workflow → version-control}/git/pull-request.service.js +10 -10
  309. package/dist/modules/version-control/git/pull-request.service.js.map +1 -0
  310. package/dist/modules/{workflow → version-control}/index.d.ts +6 -5
  311. package/dist/modules/version-control/index.d.ts.map +1 -0
  312. package/dist/modules/{workflow → version-control}/index.js +3 -2
  313. package/dist/modules/version-control/index.js.map +1 -0
  314. package/dist/modules/{workflow → version-control}/pending-commits.service.d.ts +2 -2
  315. package/dist/modules/version-control/pending-commits.service.d.ts.map +1 -0
  316. package/dist/modules/{workflow → version-control}/pending-commits.service.js +2 -2
  317. package/dist/modules/version-control/pending-commits.service.js.map +1 -0
  318. package/dist/modules/{workflow → version-control}/pending-commits.worker.d.ts +5 -5
  319. package/dist/modules/version-control/pending-commits.worker.d.ts.map +1 -0
  320. package/dist/modules/{workflow → version-control}/pending-commits.worker.js +3 -3
  321. package/dist/modules/version-control/pending-commits.worker.js.map +1 -0
  322. package/dist/modules/{workflow → version-control}/read-on-branch.d.ts +2 -2
  323. package/dist/modules/version-control/read-on-branch.d.ts.map +1 -0
  324. package/dist/modules/{workflow → version-control}/read-on-branch.js +2 -2
  325. package/dist/modules/version-control/read-on-branch.js.map +1 -0
  326. package/dist/modules/version-control/recovery-bot.d.ts.map +1 -0
  327. package/dist/modules/version-control/recovery-bot.js.map +1 -0
  328. package/dist/modules/version-control/sanitize-error.d.ts.map +1 -0
  329. package/dist/modules/version-control/sanitize-error.js.map +1 -0
  330. package/dist/modules/{workflow/workflow-hooks.d.ts → version-control/version-control-hooks.d.ts} +33 -6
  331. package/dist/modules/version-control/version-control-hooks.d.ts.map +1 -0
  332. package/dist/modules/{workflow/workflow-hooks.js → version-control/version-control-hooks.js} +20 -3
  333. package/dist/modules/version-control/version-control-hooks.js.map +1 -0
  334. package/dist/modules/{workflow/workflow.routes.d.ts → version-control/version-control.routes.d.ts} +6 -5
  335. package/dist/modules/version-control/version-control.routes.d.ts.map +1 -0
  336. package/dist/modules/{workflow/workflow.routes.js → version-control/version-control.routes.js} +61 -60
  337. package/dist/modules/version-control/version-control.routes.js.map +1 -0
  338. package/dist/modules/{workflow/workflow.service.d.ts → version-control/version-control.service.d.ts} +26 -46
  339. package/dist/modules/version-control/version-control.service.d.ts.map +1 -0
  340. package/dist/modules/{workflow/workflow.service.js → version-control/version-control.service.js} +93 -107
  341. package/dist/modules/version-control/version-control.service.js.map +1 -0
  342. package/dist/modules/workspace/agent-access.gate.d.ts +5 -5
  343. package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -1
  344. package/dist/modules/workspace/agent-access.gate.js +1 -1
  345. package/dist/modules/workspace/git-internals.middleware.d.ts +1 -1
  346. package/dist/modules/workspace/git-internals.middleware.js +2 -2
  347. package/dist/modules/workspace/kb-repository.js +1 -1
  348. package/dist/modules/workspace/kb-repository.js.map +1 -1
  349. package/dist/modules/workspace/startup/kb-git.d.ts +1 -1
  350. package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -1
  351. package/dist/modules/workspace/startup/kb-git.js +1 -1
  352. package/dist/modules/workspace/startup/kb-git.js.map +1 -1
  353. package/dist/modules/workspace/startup/steps/seed-tree.d.ts +2 -2
  354. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
  355. package/dist/modules/workspace/startup/steps/seed-tree.js +1 -1
  356. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
  357. package/dist/modules/workspace/startup/steps/template-files.step.d.ts +2 -2
  358. package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
  359. package/dist/modules/workspace/startup/steps/template-files.step.js +2 -2
  360. package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
  361. package/dist/modules/workspace/startup/steps/template-source.d.ts +17 -4
  362. package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -1
  363. package/dist/modules/workspace/startup/steps/template-source.js +8 -5
  364. package/dist/modules/workspace/startup/steps/template-source.js.map +1 -1
  365. package/dist/modules/workspace/workspace-tmp.js +3 -3
  366. package/dist/modules/workspace/workspace-tmp.js.map +1 -1
  367. package/dist/modules/workspace/workspace.routes.d.ts +4 -4
  368. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  369. package/dist/modules/workspace/workspace.routes.js +22 -22
  370. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  371. package/dist/modules/workspace/workspace.service.d.ts +6 -6
  372. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  373. package/dist/modules/workspace/workspace.service.js +11 -11
  374. package/dist/modules/workspace/workspace.service.js.map +1 -1
  375. package/dist/modules/workspace/workspace.tools.d.ts +2 -1
  376. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  377. package/dist/modules/workspace/workspace.tools.js +15 -10
  378. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  379. package/dist/shared/canonical-file-identity.d.ts +2 -2
  380. package/dist/shared/canonical-file-identity.js +5 -5
  381. package/dist/shared/canonical-file-identity.js.map +1 -1
  382. package/dist/shared/domain-errors.d.ts +39 -39
  383. package/dist/shared/domain-errors.d.ts.map +1 -1
  384. package/dist/shared/domain-errors.js +44 -44
  385. package/dist/shared/domain-errors.js.map +1 -1
  386. package/dist/shared/fire-and-forget.d.ts +14 -0
  387. package/dist/shared/fire-and-forget.d.ts.map +1 -0
  388. package/dist/shared/fire-and-forget.js +23 -0
  389. package/dist/shared/fire-and-forget.js.map +1 -0
  390. package/dist/shared/git.contract.d.ts +1 -1
  391. package/dist/shared/git.contract.js +1 -1
  392. package/dist/shared/http-errors.d.ts +3 -3
  393. package/dist/shared/http-errors.d.ts.map +1 -1
  394. package/dist/shared/http-errors.js +1 -1
  395. package/dist/shared/http-errors.js.map +1 -1
  396. package/dist/shared/pending-proposals.d.ts +3 -3
  397. package/dist/shared/pending-proposals.d.ts.map +1 -1
  398. package/dist/shared/pending-proposals.js +2 -2
  399. package/dist/shared/pending-proposals.js.map +1 -1
  400. package/dist/tenancy/static-tenant-source.js +1 -1
  401. package/dist/tenancy/static-tenant-source.js.map +1 -1
  402. package/migrations/0012_agent_audit.sql +40 -40
  403. package/migrations/0013_agent_identity_and_key_soft_delete.sql +29 -29
  404. package/migrations/meta/0012_snapshot.json +2246 -2246
  405. package/migrations/meta/0013_snapshot.json +2258 -2258
  406. package/package.json +4 -4
  407. package/src/__tests__/account-admin.ts +27 -0
  408. package/src/__tests__/capturing-logger.ts +36 -0
  409. package/src/__tests__/hexis-catalog.ts +4 -4
  410. package/src/__tests__/kb-context.ts +30 -30
  411. package/src/__tests__/kb-layout-config.test.ts +21 -0
  412. package/src/__tests__/open-change-gate.ts +1 -1
  413. package/src/__tests__/real-access.ts +16 -0
  414. package/src/__tests__/real-auth.ts +13 -0
  415. package/src/__tests__/real-readers.ts +27 -0
  416. package/src/__tests__/real-version-control.ts +22 -0
  417. package/src/__tests__/tool-handler.ts +15 -0
  418. package/src/__tests__/tool-wiring.ts +10 -4
  419. package/src/__tests__/workspace-service.ts +1 -1
  420. package/src/core/__tests__/catalog-cache-invalidation.test.ts +3 -3
  421. package/src/core/__tests__/commit-announcements-wired-once.test.ts +53 -0
  422. package/src/core/__tests__/compose-embed.test.ts +1 -1
  423. package/src/core/__tests__/damaged-document.tools.test.ts +3 -3
  424. package/src/core/__tests__/erasure-slices-wired-once.test.ts +46 -0
  425. package/src/core/__tests__/gated-boot.test.ts +2 -2
  426. package/src/core/__tests__/lifecycle.test.ts +9 -9
  427. package/src/core/catalog-cache-invalidation.ts +2 -2
  428. package/src/core/compose-embed.ts +3 -3
  429. package/src/core/core-ports.ts +19 -4
  430. package/src/core/create-core-server.ts +39 -25
  431. package/src/core/create-core-services.ts +83 -65
  432. package/src/core/lifecycle.ts +9 -9
  433. package/src/core-config.ts +3 -3
  434. package/src/index.ts +5 -5
  435. package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +4 -4
  436. package/src/modules/access/__tests__/access-own-read-grant.test.ts +14 -14
  437. package/src/modules/access/__tests__/access-requests.routes.test.ts +23 -23
  438. package/src/modules/access/__tests__/access.routes.admins.test.ts +4 -4
  439. package/src/modules/access/__tests__/access.routes.batch.test.ts +4 -4
  440. package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +6 -6
  441. package/src/modules/access/__tests__/access.routes.group-grant.test.ts +6 -6
  442. package/src/modules/access/__tests__/access.routes.overrides.test.ts +5 -5
  443. package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +6 -6
  444. package/src/modules/access/__tests__/access.routes.prospective.test.ts +3 -3
  445. package/src/modules/access/__tests__/access.routes.revoke.test.ts +6 -6
  446. package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +6 -6
  447. package/src/modules/access/__tests__/admin-locked-commit.test.ts +6 -6
  448. package/src/modules/access/__tests__/admin-route-helpers.test.ts +5 -5
  449. package/src/modules/access/__tests__/groups-admin.service.test.ts +44 -44
  450. package/src/modules/access/__tests__/roles-admin.security.test.ts +14 -13
  451. package/src/modules/access/__tests__/roles-admin.service.test.ts +34 -34
  452. package/src/modules/access/__tests__/roles-capabilities.test.ts +29 -29
  453. package/src/modules/access/__tests__/roles.routes.test.ts +11 -11
  454. package/src/modules/access/__tests__/synced-groups-committer.test.ts +14 -14
  455. package/src/modules/access/__tests__/user-access-removal.service.test.ts +33 -33
  456. package/src/modules/access/access-control.service.ts +1 -1
  457. package/src/modules/access/access-declarations.ts +2 -2
  458. package/src/modules/access/access-mutation.service.ts +2 -2
  459. package/src/modules/access/access-requests.routes.ts +7 -7
  460. package/src/modules/access/access-requests.service.ts +13 -13
  461. package/src/modules/access/access.routes.ts +11 -11
  462. package/src/modules/access/admin-locked-commit.ts +11 -11
  463. package/src/modules/access/admin-route-helpers.ts +5 -5
  464. package/src/modules/access/change-read-gate.ts +1 -1
  465. package/src/modules/access/groups-admin.service.ts +7 -7
  466. package/src/modules/access/reference-scan.ts +1 -1
  467. package/src/modules/access/roles-admin.service.ts +9 -9
  468. package/src/modules/access/synced-groups-committer.ts +11 -11
  469. package/src/modules/access/user-access-removal.service.ts +7 -7
  470. package/src/modules/access-model/__tests__/access-grammar.test.ts +2 -2
  471. package/src/modules/access-model/access-errors.ts +4 -4
  472. package/src/modules/access-model/access-grammar.ts +4 -4
  473. package/src/modules/access-model/change-gate.ts +1 -1
  474. package/src/modules/access-model/roles-yaml-guard.ts +4 -4
  475. package/src/modules/agent-guide/__tests__/agent-guide.test.ts +19 -4
  476. package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +1 -1
  477. package/src/modules/agent-guide/__tests__/guide-matches-code.test.ts +2 -2
  478. package/src/modules/agent-guide/agent-guide.ts +48 -22
  479. package/src/modules/agent-guide/guide-budget.ts +6 -2
  480. package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +4 -5
  481. package/src/modules/agent-instructions/__tests__/compose.test.ts +1 -1
  482. package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +113 -7
  483. package/src/modules/agent-instructions/agent-instructions.routes.ts +12 -5
  484. package/src/modules/agent-instructions/compose.ts +24 -9
  485. package/src/modules/agent-instructions/shared-file-rules.ts +53 -11
  486. package/src/modules/agent-scope/__tests__/agent-surfaces.connect-as.test.ts +1 -1
  487. package/src/modules/audit/__tests__/agent-rest-audit.middleware.test.ts +113 -113
  488. package/src/modules/audit/__tests__/event-classifier.test.ts +121 -121
  489. package/src/modules/audit/event-classifier.ts +155 -155
  490. package/src/modules/auth/__tests__/account-deactivation.test.ts +1 -1
  491. package/src/modules/auth/__tests__/account-erasure.approval-gate.test.ts +4 -4
  492. package/src/modules/auth/__tests__/account-erasure.participants.test.ts +56 -0
  493. package/src/modules/auth/__tests__/account-switch-lock.pg.test.ts +2 -2
  494. package/src/modules/auth/__tests__/account-switch-lock.test.ts +2 -2
  495. package/src/modules/auth/__tests__/account.routes.test.ts +73 -11
  496. package/src/modules/auth/__tests__/auth.service.test.ts +1 -1
  497. package/src/modules/auth/account-erasure.service.ts +27 -9
  498. package/src/modules/auth/account-hooks.ts +34 -0
  499. package/src/modules/auth/account-switch-lock.ts +2 -2
  500. package/src/modules/auth/account.routes.ts +23 -5
  501. package/src/modules/auth/auth.service.ts +4 -4
  502. package/src/modules/auth/index.ts +1 -0
  503. package/src/modules/database/__tests__/advisory-lock.test.ts +169 -169
  504. package/src/modules/database/__tests__/connection.pg.test.ts +55 -0
  505. package/src/modules/database/__tests__/connection.test.ts +30 -0
  506. package/src/modules/database/__tests__/migrations-unqualified.test.ts +32 -32
  507. package/src/modules/database/connection.ts +12 -0
  508. package/src/modules/database/core-schema.ts +2 -2
  509. package/src/modules/diff/__tests__/diff.routes.read-gate.test.ts +2 -2
  510. package/src/modules/diff/__tests__/diff.routes.rejectPathsLocked.test.ts +12 -12
  511. package/src/modules/diff/diff.routes.ts +8 -8
  512. package/src/modules/embed/__tests__/embed.service.lock.test.ts +7 -7
  513. package/src/modules/embed/__tests__/embed.service.test.ts +48 -62
  514. package/src/modules/embed/__tests__/embed.tools.test.ts +1 -1
  515. package/src/modules/embed/embed.routes.ts +1 -1
  516. package/src/modules/embed/embed.service.ts +17 -17
  517. package/src/modules/kb-fs/__tests__/branch-name.test.ts +2 -2
  518. package/src/modules/kb-fs/__tests__/file-change-notifier.test.ts +23 -4
  519. package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +264 -305
  520. package/src/modules/kb-fs/__tests__/move-with-edits.test.ts +103 -40
  521. package/src/modules/kb-fs/__tests__/repo-path.test.ts +8 -8
  522. package/src/modules/kb-fs/branch-name.ts +8 -8
  523. package/src/modules/kb-fs/file-change-notifier.ts +55 -32
  524. package/src/modules/kb-fs/locking-filesystem.ts +34 -48
  525. package/src/modules/kb-fs/repo-path.ts +5 -5
  526. package/src/modules/kb-fs/tmp-ref.ts +3 -3
  527. package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +36 -36
  528. package/src/modules/kb-sync/index.ts +1 -1
  529. package/src/modules/kb-sync/kb-sync.interface.ts +3 -3
  530. package/src/modules/kb-sync/kb-sync.service.ts +9 -9
  531. package/src/modules/marketplace/__tests__/github-facade.test.ts +1 -1
  532. package/src/modules/marketplace/__tests__/marketplace-git.test.ts +1 -1
  533. package/src/modules/mcp/__tests__/mcp-proxy-helpers.test.ts +4 -2
  534. package/src/modules/mcp/__tests__/mcp.e2e.test.ts +69 -8
  535. package/src/modules/mcp/__tests__/mcp.service.test.ts +91 -13
  536. package/src/modules/mcp/__tests__/read-only-hint.pg.test.ts +194 -0
  537. package/src/modules/mcp/mcp.service.ts +89 -28
  538. package/src/modules/onboarding/__tests__/starter-pack.service.test.ts +59 -59
  539. package/src/modules/onboarding/onboarding.contract.ts +1 -1
  540. package/src/modules/onboarding/onboarding.routes.ts +2 -2
  541. package/src/modules/onboarding/starter-pack.service.ts +7 -9
  542. package/src/modules/plugins/__tests__/bundle-dialect.e2e.test.ts +1 -1
  543. package/src/modules/plugins/__tests__/compile-marketplace.test.ts +1 -1
  544. package/src/modules/plugins/__tests__/join-request-jobs.service.test.ts +43 -43
  545. package/src/modules/plugins/__tests__/join-requests.service.test.ts +19 -19
  546. package/src/modules/plugins/__tests__/join-requests.settlement.test.ts +26 -26
  547. package/src/modules/plugins/__tests__/plugin-rename.service.test.ts +46 -21
  548. package/src/modules/plugins/__tests__/plugins.routes.test.ts +31 -31
  549. package/src/modules/plugins/join-request-jobs.service.ts +10 -10
  550. package/src/modules/plugins/join-requests.service.ts +4 -4
  551. package/src/modules/plugins/plugin-rename.service.ts +1 -1
  552. package/src/modules/plugins/plugins.routes.ts +9 -9
  553. package/src/modules/secrets-vault/__tests__/db-secrets-vault.managed.test.ts +773 -0
  554. package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +28 -1
  555. package/src/modules/secrets-vault/__tests__/managed-sign-in.route.test.ts +351 -0
  556. package/src/modules/secrets-vault/db-secrets-vault.service.ts +562 -95
  557. package/src/modules/secrets-vault/index.ts +7 -0
  558. package/src/modules/secrets-vault/managed-sign-in-erasure.ts +41 -0
  559. package/src/modules/secrets-vault/secrets-variable-loader.ts +134 -134
  560. package/src/modules/secrets-vault/secrets-vault.contract.ts +152 -0
  561. package/src/modules/secrets-vault/secrets-vault.routes.ts +157 -10
  562. package/src/modules/settings/__tests__/repository-source.test.ts +1 -1
  563. package/src/modules/settings/__tests__/setup.routes.layout-phase.test.ts +1 -1
  564. package/src/modules/settings/__tests__/setup.routes.managed-phase.test.ts +1 -1
  565. package/src/modules/settings/connection-check.ts +1 -1
  566. package/src/modules/settings/deployment-settings.service.ts +1 -1
  567. package/src/modules/settings/git-root-folders.ts +1 -1
  568. package/src/modules/settings/setup.routes.ts +1 -1
  569. package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +3 -3
  570. package/src/modules/skills/__tests__/branch-skills.tools.test.ts +1 -1
  571. package/src/modules/skills/__tests__/pending-skills.service.test.ts +8 -8
  572. package/src/modules/skills/__tests__/skill-access-requests.routes.test.ts +18 -18
  573. package/src/modules/skills/__tests__/skills.service.test.ts +3 -3
  574. package/src/modules/skills/pending-skills.service.ts +3 -3
  575. package/src/modules/skills/skill-access-requests.routes.ts +7 -7
  576. package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +4 -4
  577. package/src/modules/tool-helpers/__tests__/branch-declaration.test.ts +3 -3
  578. package/src/modules/tool-helpers/__tests__/can-write.tools.test.ts +3 -3
  579. package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +4 -4
  580. package/src/modules/tool-helpers/__tests__/tools-surface.test.ts +3 -3
  581. package/src/modules/tool-helpers/__tests__/validate-token.test.ts +1 -1
  582. package/src/modules/tool-helpers/tool-context.ts +6 -6
  583. package/src/modules/tool-helpers/tool-handler.ts +2 -2
  584. package/src/modules/tool-helpers/tool.contract.ts +4 -4
  585. package/src/modules/tool-manuals/__tests__/pending-tools.service.test.ts +9 -9
  586. package/src/modules/tool-manuals/__tests__/tool-manuals.managed-sign-in.test.ts +89 -0
  587. package/src/modules/tool-manuals/__tests__/tool-manuals.restart.test.ts +1 -1
  588. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +36 -1
  589. package/src/modules/tool-manuals/mcp-server-edit.service.ts +1 -1
  590. package/src/modules/tool-manuals/pending-tools.service.ts +3 -3
  591. package/src/modules/tool-manuals/tool-delete.service.ts +1 -1
  592. package/src/modules/tool-manuals/tool-manuals.contract.ts +41 -3
  593. package/src/modules/tool-manuals/tool-manuals.service.ts +53 -5
  594. package/src/modules/tool-manuals/tool-manuals.tools.ts +41 -15
  595. package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +4 -4
  596. package/src/modules/tool-registry/__tests__/read-only-hints.test.ts +3 -3
  597. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +6 -6
  598. package/src/modules/{workflow → version-control}/__tests__/apply-failure.test.ts +54 -54
  599. package/src/modules/{workflow → version-control}/__tests__/event-bus.test.ts +27 -27
  600. package/src/modules/{workflow → version-control}/__tests__/events-delivery.test.ts +37 -37
  601. package/src/modules/{workflow → version-control}/__tests__/file-lock.service.deletion-gate.test.ts +2 -2
  602. package/src/modules/{workflow → version-control}/__tests__/file-lock.service.path-identity.test.ts +8 -8
  603. package/src/modules/{workflow → version-control}/__tests__/merge-announces-tree.test.ts +11 -11
  604. package/src/modules/{workflow → version-control}/__tests__/pending-commits.worker.concurrency.test.ts +9 -9
  605. package/src/modules/{workflow → version-control}/__tests__/pending-commits.worker.test.ts +25 -25
  606. package/src/modules/{workflow → version-control}/__tests__/preserve-roles-yaml.test.ts +42 -42
  607. package/src/modules/version-control/__tests__/public-surface.test.ts +114 -0
  608. package/src/modules/{workflow/__tests__/workflow.routes.folder-proposals.test.ts → version-control/__tests__/version-control.routes.folder-proposals.test.ts} +18 -18
  609. 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
  610. 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
  611. package/src/modules/{workflow/__tests__/workflow.routes.lock-path.test.ts → version-control/__tests__/version-control.routes.lock-path.test.ts} +18 -18
  612. 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
  613. package/src/modules/{workflow/__tests__/workflow.routes.push-refused.test.ts → version-control/__tests__/version-control.routes.push-refused.test.ts} +14 -14
  614. 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
  615. package/src/modules/{workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts → version-control/__tests__/version-control.service.commitFileWhileLocked.test.ts} +14 -14
  616. package/src/modules/{workflow/__tests__/workflow.service.delete-branch.test.ts → version-control/__tests__/version-control.service.delete-branch.test.ts} +14 -14
  617. 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
  618. package/src/modules/{workflow/__tests__/workflow.service.facade.test.ts → version-control/__tests__/version-control.service.facade.test.ts} +100 -102
  619. package/src/modules/{workflow/__tests__/workflow.service.folder-proposals.test.ts → version-control/__tests__/version-control.service.folder-proposals.test.ts} +7 -7
  620. package/src/modules/{workflow/__tests__/workflow.service.platform-restore.test.ts → version-control/__tests__/version-control.service.platform-restore.test.ts} +6 -6
  621. package/src/modules/{workflow/__tests__/workflow.service.read-gate.test.ts → version-control/__tests__/version-control.service.read-gate.test.ts} +9 -9
  622. package/src/modules/{workflow/__tests__/workflow.service.releaseLock.test.ts → version-control/__tests__/version-control.service.releaseLock.test.ts} +85 -75
  623. package/src/modules/{workflow/__tests__/workflow.service.repository-replaced.test.ts → version-control/__tests__/version-control.service.repository-replaced.test.ts} +4 -4
  624. package/src/modules/{workflow/__tests__/workflow.service.sync.test.ts → version-control/__tests__/version-control.service.sync.test.ts} +17 -17
  625. 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
  626. 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
  627. 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
  628. package/src/modules/{workflow → version-control}/agent-tools/__tests__/change-request-read.tools.test.ts +3 -3
  629. package/src/modules/{workflow → version-control}/agent-tools/__tests__/update-change-request.parity.test.ts +16 -16
  630. package/src/modules/{workflow/agent-tools/__tests__/workflow.tools.test.ts → version-control/agent-tools/__tests__/version-control.tools.test.ts} +9 -9
  631. package/src/modules/{workflow → version-control}/agent-tools/change-request-read.tools.ts +2 -2
  632. package/src/modules/{workflow/agent-tools/workflow.tools.ts → version-control/agent-tools/version-control.tools.ts} +30 -30
  633. package/src/modules/{workflow → version-control}/change-request-hooks.ts +1 -1
  634. package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/approval-states.test.ts +5 -5
  635. package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/cancel-pr.test.ts +8 -8
  636. package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/carry-approvals-forward.test.ts +6 -6
  637. package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/erase-approver.test.ts +5 -5
  638. package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/merge-gate.test.ts +3 -3
  639. package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/merge-records-own-commit.test.ts +3 -3
  640. package/src/modules/{workflow/review-workflow/review-workflow.interface.ts → version-control/change-request-review/change-request-review.interface.ts} +1 -1
  641. package/src/modules/{workflow/review-workflow/review-workflow.service.ts → version-control/change-request-review/change-request-review.service.ts} +28 -28
  642. package/src/modules/{workflow → version-control}/event-bus.ts +14 -14
  643. package/src/modules/{workflow → version-control}/events-delivery.ts +11 -11
  644. package/src/modules/{workflow → version-control}/events.routes.ts +5 -5
  645. package/src/modules/{workflow → version-control}/file-lock.service.ts +6 -6
  646. package/src/modules/{workflow → version-control}/git/__tests__/git-test-helpers.ts +3 -3
  647. package/src/modules/{workflow → version-control}/git/__tests__/git.service.accessGating.test.ts +5 -5
  648. package/src/modules/{workflow → version-control}/git/__tests__/git.service.appliedChange.test.ts +4 -4
  649. package/src/modules/{workflow → version-control}/git/__tests__/git.service.changedFilesForPr.test.ts +16 -16
  650. package/src/modules/{workflow → version-control}/git/__tests__/git.service.changedPathsMemo.test.ts +2 -2
  651. package/src/modules/{workflow → version-control}/git/__tests__/git.service.commitChanges.test.ts +3 -3
  652. package/src/modules/{workflow → version-control}/git/__tests__/git.service.commitFile.strayPath.test.ts +10 -10
  653. package/src/modules/{workflow → version-control}/git/__tests__/git.service.commitFile.test.ts +3 -3
  654. package/src/modules/version-control/git/__tests__/git.service.commitLanded.test.ts +199 -0
  655. package/src/modules/{workflow → version-control}/git/__tests__/git.service.createBranch.test.ts +3 -3
  656. package/src/modules/{workflow → version-control}/git/__tests__/git.service.deleteBranch.test.ts +7 -7
  657. package/src/modules/{workflow → version-control}/git/__tests__/git.service.diffFileAtCommit.test.ts +9 -9
  658. package/src/modules/{workflow → version-control}/git/__tests__/git.service.diffFileBetweenBranches.test.ts +23 -23
  659. package/src/modules/{workflow → version-control}/git/__tests__/git.service.fileBytesAtCommit.test.ts +7 -7
  660. package/src/modules/{workflow → version-control}/git/__tests__/git.service.fileContentsAtCommit.test.ts +3 -3
  661. package/src/modules/{workflow → version-control}/git/__tests__/git.service.forkPoint.test.ts +2 -2
  662. package/src/modules/{workflow → version-control}/git/__tests__/git.service.history-guards.test.ts +2 -2
  663. package/src/modules/{workflow → version-control}/git/__tests__/git.service.list-branches.test.ts +12 -12
  664. package/src/modules/{workflow → version-control}/git/__tests__/git.service.listTrackedFiles.test.ts +2 -2
  665. package/src/modules/{workflow → version-control}/git/__tests__/git.service.logForFile.test.ts +6 -6
  666. package/src/modules/{workflow → version-control}/git/__tests__/git.service.mergeChangeRequest.test.ts +99 -22
  667. package/src/modules/{workflow → version-control}/git/__tests__/git.service.mergeIntoBranch.test.ts +16 -16
  668. package/src/modules/{workflow → version-control}/git/__tests__/git.service.pendingChanges.test.ts +2 -2
  669. package/src/modules/{workflow → version-control}/git/__tests__/git.service.postMergePull.test.ts +5 -5
  670. package/src/modules/{workflow → version-control}/git/__tests__/git.service.prFetchFailure.test.ts +2 -2
  671. package/src/modules/{workflow → version-control}/git/__tests__/git.service.pull.test.ts +12 -12
  672. package/src/modules/{workflow → version-control}/git/__tests__/git.service.readFileAtRef.test.ts +3 -3
  673. package/src/modules/{workflow → version-control}/git/__tests__/git.service.revertPathsAndPush.test.ts +2 -2
  674. package/src/modules/{workflow → version-control}/git/__tests__/git.service.status.test.ts +4 -4
  675. package/src/modules/{workflow → version-control}/git/__tests__/git.service.syncFromRemote.test.ts +8 -8
  676. package/src/modules/{workflow → version-control}/git/__tests__/git.service.worktrees.test.ts +4 -4
  677. package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.list-fetch.test.ts +2 -2
  678. package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.test.ts +6 -6
  679. package/src/modules/{workflow → version-control}/git/git.service.ts +112 -59
  680. package/src/modules/{workflow → version-control}/git/node-git-runner.ts +1 -1
  681. package/src/modules/{workflow → version-control}/git/pull-request.service.ts +12 -12
  682. package/src/modules/{workflow → version-control}/index.ts +6 -5
  683. package/src/modules/{workflow → version-control}/pending-commits.service.ts +2 -2
  684. package/src/modules/{workflow → version-control}/pending-commits.worker.ts +8 -8
  685. package/src/modules/{workflow → version-control}/read-on-branch.ts +2 -2
  686. package/src/modules/{workflow/workflow-hooks.ts → version-control/version-control-hooks.ts} +44 -5
  687. package/src/modules/{workflow/workflow.routes.ts → version-control/version-control.routes.ts} +64 -63
  688. package/src/modules/{workflow/workflow.service.ts → version-control/version-control.service.ts} +96 -109
  689. package/src/modules/workspace/__tests__/agent-access.gate.test.ts +8 -8
  690. package/src/modules/workspace/__tests__/agent-downloads.test.ts +13 -16
  691. package/src/modules/workspace/__tests__/agent-uploads.test.ts +11 -12
  692. package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +11 -16
  693. package/src/modules/workspace/__tests__/file-stat-access.test.ts +3 -3
  694. package/src/modules/workspace/__tests__/git-internals.security.test.ts +27 -27
  695. package/src/modules/workspace/__tests__/kb-repository.test.ts +1 -1
  696. package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +3 -3
  697. package/src/modules/workspace/__tests__/start-session.default-branch.test.ts +152 -0
  698. package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +6 -6
  699. package/src/modules/workspace/__tests__/workspace-tmp.tools.test.ts +13 -14
  700. package/src/modules/workspace/__tests__/workspace.routes.at-ref.test.ts +6 -6
  701. package/src/modules/workspace/__tests__/workspace.routes.branch-errors.test.ts +4 -4
  702. package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +6 -6
  703. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +8 -8
  704. package/src/modules/workspace/__tests__/workspace.routes.download-implies-read.test.ts +4 -4
  705. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +6 -6
  706. package/src/modules/workspace/__tests__/workspace.routes.file-writes.test.ts +6 -6
  707. package/src/modules/workspace/__tests__/workspace.routes.folder-zip-access.test.ts +4 -4
  708. package/src/modules/workspace/__tests__/workspace.routes.folders.test.ts +6 -6
  709. package/src/modules/workspace/__tests__/workspace.routes.move-platform-files.test.ts +7 -7
  710. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +13 -13
  711. package/src/modules/workspace/__tests__/workspace.service.any-workspace-credentials.test.ts +1 -1
  712. package/src/modules/workspace/__tests__/workspace.service.replaced-repository.test.ts +1 -1
  713. package/src/modules/workspace/__tests__/workspace.service.worktrees.test.ts +1 -1
  714. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +5 -5
  715. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +5 -5
  716. package/src/modules/workspace/__tests__/workspace.tools.test.ts +27 -27
  717. package/src/modules/workspace/agent-access.gate.ts +5 -5
  718. package/src/modules/workspace/git-internals.middleware.ts +2 -2
  719. package/src/modules/workspace/kb-repository.ts +1 -1
  720. package/src/modules/workspace/startup/__tests__/kb-startup-runner.degraded.test.ts +1 -1
  721. package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +1 -1
  722. package/src/modules/workspace/startup/__tests__/kb-startup-runner.worktrees.test.ts +1 -1
  723. package/src/modules/workspace/startup/kb-git.ts +1 -1
  724. package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +94 -70
  725. package/src/modules/workspace/startup/steps/seed-tree.ts +4 -5
  726. package/src/modules/workspace/startup/steps/template-files.step.ts +4 -5
  727. package/src/modules/workspace/startup/steps/template-source.ts +21 -6
  728. package/src/modules/workspace/workspace-tmp.ts +4 -4
  729. package/src/modules/workspace/workspace.routes.ts +26 -26
  730. package/src/modules/workspace/workspace.service.ts +11 -11
  731. package/src/modules/workspace/workspace.tools.ts +17 -10
  732. package/src/shared/__tests__/canonical-file-identity.test.ts +5 -5
  733. package/src/shared/__tests__/fire-and-forget.test.ts +39 -0
  734. package/src/shared/__tests__/http-errors.test.ts +4 -4
  735. package/src/shared/canonical-file-identity.ts +5 -5
  736. package/src/shared/domain-errors.ts +44 -44
  737. package/src/shared/fire-and-forget.ts +26 -0
  738. package/src/shared/git.contract.ts +1 -1
  739. package/src/shared/http-errors.ts +3 -3
  740. package/src/shared/kb-context.ts +130 -130
  741. package/src/shared/pending-proposals.ts +4 -4
  742. package/src/tenancy/__tests__/tenant-host.test.ts +372 -372
  743. package/src/tenancy/__tests__/tenant-runtime.test.ts +221 -221
  744. package/src/tenancy/__tests__/tenant-secrets.test.ts +35 -35
  745. package/src/tenancy/index.ts +35 -35
  746. package/src/tenancy/static-tenant-source.ts +1 -1
  747. package/src/tenancy/tenant-host.ts +261 -261
  748. package/src/tenancy/tenant-runtime.ts +208 -208
  749. package/src/tenancy/tenant-source.contract.ts +59 -59
  750. package/dist/modules/workflow/agent-tools/change-request-read-shape.d.ts.map +0 -1
  751. package/dist/modules/workflow/agent-tools/change-request-read-shape.js.map +0 -1
  752. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +0 -1
  753. package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +0 -1
  754. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +0 -1
  755. package/dist/modules/workflow/agent-tools/change-request-summary.js.map +0 -1
  756. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +0 -1
  757. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +0 -1
  758. package/dist/modules/workflow/change-request-hooks.d.ts.map +0 -1
  759. package/dist/modules/workflow/change-request-hooks.js.map +0 -1
  760. package/dist/modules/workflow/event-bus.d.ts.map +0 -1
  761. package/dist/modules/workflow/event-bus.js.map +0 -1
  762. package/dist/modules/workflow/events-delivery.d.ts.map +0 -1
  763. package/dist/modules/workflow/events-delivery.js.map +0 -1
  764. package/dist/modules/workflow/events.routes.d.ts.map +0 -1
  765. package/dist/modules/workflow/events.routes.js.map +0 -1
  766. package/dist/modules/workflow/file-lock.service.d.ts.map +0 -1
  767. package/dist/modules/workflow/file-lock.service.js.map +0 -1
  768. package/dist/modules/workflow/git/change-request-link.d.ts.map +0 -1
  769. package/dist/modules/workflow/git/change-request-link.js.map +0 -1
  770. package/dist/modules/workflow/git/git-version.d.ts.map +0 -1
  771. package/dist/modules/workflow/git/git-version.js.map +0 -1
  772. package/dist/modules/workflow/git/git.service.d.ts.map +0 -1
  773. package/dist/modules/workflow/git/git.service.js.map +0 -1
  774. package/dist/modules/workflow/git/merge-commit.d.ts.map +0 -1
  775. package/dist/modules/workflow/git/merge-commit.js.map +0 -1
  776. package/dist/modules/workflow/git/node-git-runner.d.ts.map +0 -1
  777. package/dist/modules/workflow/git/node-git-runner.js.map +0 -1
  778. package/dist/modules/workflow/git/pull-request.service.d.ts.map +0 -1
  779. package/dist/modules/workflow/git/pull-request.service.js.map +0 -1
  780. package/dist/modules/workflow/index.d.ts.map +0 -1
  781. package/dist/modules/workflow/index.js.map +0 -1
  782. package/dist/modules/workflow/pending-commits.service.d.ts.map +0 -1
  783. package/dist/modules/workflow/pending-commits.service.js.map +0 -1
  784. package/dist/modules/workflow/pending-commits.worker.d.ts.map +0 -1
  785. package/dist/modules/workflow/pending-commits.worker.js.map +0 -1
  786. package/dist/modules/workflow/read-on-branch.d.ts.map +0 -1
  787. package/dist/modules/workflow/read-on-branch.js.map +0 -1
  788. package/dist/modules/workflow/recovery-bot.d.ts.map +0 -1
  789. package/dist/modules/workflow/recovery-bot.js.map +0 -1
  790. package/dist/modules/workflow/review-workflow/approval-lock.d.ts.map +0 -1
  791. package/dist/modules/workflow/review-workflow/approval-lock.js.map +0 -1
  792. package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts.map +0 -1
  793. package/dist/modules/workflow/review-workflow/review-workflow.interface.js +0 -2
  794. package/dist/modules/workflow/review-workflow/review-workflow.interface.js.map +0 -1
  795. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +0 -1
  796. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +0 -1
  797. package/dist/modules/workflow/sanitize-error.d.ts.map +0 -1
  798. package/dist/modules/workflow/sanitize-error.js.map +0 -1
  799. package/dist/modules/workflow/workflow-hooks.d.ts.map +0 -1
  800. package/dist/modules/workflow/workflow-hooks.js.map +0 -1
  801. package/dist/modules/workflow/workflow.routes.d.ts.map +0 -1
  802. package/dist/modules/workflow/workflow.routes.js.map +0 -1
  803. package/dist/modules/workflow/workflow.service.d.ts.map +0 -1
  804. package/dist/modules/workflow/workflow.service.js.map +0 -1
  805. package/src/modules/mcp/__tests__/read-only-hint.e2e.test.ts +0 -213
  806. /package/dist/modules/{workflow → version-control}/agent-tools/change-request-read-shape.d.ts +0 -0
  807. /package/dist/modules/{workflow → version-control}/agent-tools/change-request-read-shape.js +0 -0
  808. /package/dist/modules/{workflow → version-control}/agent-tools/change-request-read.tools.d.ts +0 -0
  809. /package/dist/modules/{workflow → version-control}/agent-tools/change-request-summary.d.ts +0 -0
  810. /package/dist/modules/{workflow → version-control}/agent-tools/change-request-summary.js +0 -0
  811. /package/dist/modules/{workflow → version-control}/change-request-hooks.js +0 -0
  812. /package/dist/modules/{workflow/review-workflow → version-control/change-request-review}/approval-lock.d.ts +0 -0
  813. /package/dist/modules/{workflow/review-workflow → version-control/change-request-review}/approval-lock.js +0 -0
  814. /package/dist/modules/{workflow → version-control}/events-delivery.js +0 -0
  815. /package/dist/modules/{workflow → version-control}/events.routes.js +0 -0
  816. /package/dist/modules/{workflow → version-control}/git/change-request-link.d.ts +0 -0
  817. /package/dist/modules/{workflow → version-control}/git/change-request-link.js +0 -0
  818. /package/dist/modules/{workflow → version-control}/git/git-version.d.ts +0 -0
  819. /package/dist/modules/{workflow → version-control}/git/git-version.js +0 -0
  820. /package/dist/modules/{workflow → version-control}/git/merge-commit.d.ts +0 -0
  821. /package/dist/modules/{workflow → version-control}/git/merge-commit.js +0 -0
  822. /package/dist/modules/{workflow → version-control}/git/node-git-runner.d.ts +0 -0
  823. /package/dist/modules/{workflow → version-control}/recovery-bot.d.ts +0 -0
  824. /package/dist/modules/{workflow → version-control}/recovery-bot.js +0 -0
  825. /package/dist/modules/{workflow → version-control}/sanitize-error.d.ts +0 -0
  826. /package/dist/modules/{workflow → version-control}/sanitize-error.js +0 -0
  827. /package/src/modules/{workflow → version-control}/__tests__/change-request-hooks.test.ts +0 -0
  828. /package/src/modules/{workflow → version-control}/__tests__/fake-file-lock-db.ts +0 -0
  829. /package/src/modules/{workflow → version-control}/__tests__/pending-commits.canonical.test.ts +0 -0
  830. /package/src/modules/{workflow → version-control}/__tests__/pending-commits.enqueue-if-absent.test.ts +0 -0
  831. /package/src/modules/{workflow → version-control}/__tests__/pending-commits.repository-replaced.test.ts +0 -0
  832. /package/src/modules/{workflow → version-control}/__tests__/sanitize-error.test.ts +0 -0
  833. /package/src/modules/{workflow → version-control}/agent-tools/__tests__/change-request-read-shape.test.ts +0 -0
  834. /package/src/modules/{workflow → version-control}/agent-tools/change-request-read-shape.ts +0 -0
  835. /package/src/modules/{workflow → version-control}/agent-tools/change-request-summary.ts +0 -0
  836. /package/src/modules/{workflow/review-workflow → version-control/change-request-review}/approval-lock.ts +0 -0
  837. /package/src/modules/{workflow → version-control}/git/__tests__/branchAuthor.test.ts +0 -0
  838. /package/src/modules/{workflow → version-control}/git/__tests__/change-request-link.test.ts +0 -0
  839. /package/src/modules/{workflow → version-control}/git/__tests__/git-version.failure.test.ts +0 -0
  840. /package/src/modules/{workflow → version-control}/git/__tests__/git-version.test.ts +0 -0
  841. /package/src/modules/{workflow → version-control}/git/__tests__/node-git-runner.test.ts +0 -0
  842. /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.getPrDetail.test.ts +0 -0
  843. /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.placeholder.test.ts +0 -0
  844. /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.viewer-can-cancel.test.ts +0 -0
  845. /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.viewer-can-delete.test.ts +0 -0
  846. /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.viewer-can-update.test.ts +0 -0
  847. /package/src/modules/{workflow → version-control}/git/change-request-link.ts +0 -0
  848. /package/src/modules/{workflow → version-control}/git/git-version.ts +0 -0
  849. /package/src/modules/{workflow → version-control}/git/merge-commit.ts +0 -0
  850. /package/src/modules/{workflow → version-control}/recovery-bot.ts +0 -0
  851. /package/src/modules/{workflow → version-control}/sanitize-error.ts +0 -0
@@ -0,0 +1,773 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { DbSecretsVaultService } from '../db-secrets-vault.service.js';
3
+ import { managedSignInErasure } from '../managed-sign-in-erasure.js';
4
+ import { SecretOAuthError, type ManagedTokenRefresher, type ManagedTokenRevoker } from '../secrets-vault.contract.js';
5
+ import { TokenCrypto } from '../../../shared/token-crypto.js';
6
+ import type { Database } from '../../database/index.js';
7
+
8
+ /**
9
+ * A MANAGED sign-in: the tokens arrive from the broker through a handoff the
10
+ * row was told to expect, renew through the broker, and are revoked with the
11
+ * provider before the row goes. Everything else about the row is an ordinary
12
+ * OAuth row, which the oauth suite covers.
13
+ */
14
+
15
+ const ENC_KEY = Buffer.alloc(32, 9).toString('base64');
16
+ const crypto = new TokenCrypto(ENC_KEY);
17
+ const KEY = 'microsoft365_ACCESS_TOKEN';
18
+ const USER = 'user-1';
19
+ const NONCE = 'n0nce-n0nce-n0nce-n0nce';
20
+
21
+ const MANAGED_META = {
22
+ authorizationUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
23
+ tokenUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
24
+ clientId: '',
25
+ scopes: ['Mail.Read'],
26
+ authParams: {},
27
+ managed: { connectorId: 'microsoft365', scopeSet: 'read' },
28
+ };
29
+
30
+ /** The sequential fake drizzle chain the oauth suite uses, capturing what is written. */
31
+ function makeFakeDb(queue: unknown[]) {
32
+ const captured: { values: Record<string, unknown>[]; set: Record<string, unknown>[]; where: unknown[]; for: unknown[] } = {
33
+ values: [],
34
+ set: [],
35
+ where: [],
36
+ for: [],
37
+ };
38
+ function nextChain() {
39
+ const result = queue.shift();
40
+ const chain: Record<string, unknown> = {};
41
+ const passthrough = (recorder?: (args: unknown[]) => void) =>
42
+ vi.fn((...args: unknown[]) => {
43
+ recorder?.(args);
44
+ return chain;
45
+ });
46
+ chain.values = passthrough((a) => captured.values.push(a[0] as Record<string, unknown>));
47
+ chain.set = passthrough((a) => captured.set.push(a[0] as Record<string, unknown>));
48
+ chain.where = passthrough((a) => captured.where.push(a[0]));
49
+ chain.limit = passthrough();
50
+ chain.from = passthrough();
51
+ chain.returning = passthrough();
52
+ chain.onConflictDoUpdate = passthrough();
53
+ chain.for = passthrough((a) => captured.for.push(a[0]));
54
+ chain.then = (onF: (v: unknown) => unknown, onR: (e: unknown) => unknown) => Promise.resolve(result).then(onF, onR);
55
+ return chain;
56
+ }
57
+ const db = {
58
+ insert: vi.fn(() => nextChain()),
59
+ select: vi.fn(() => nextChain()),
60
+ update: vi.fn(() => nextChain()),
61
+ delete: vi.fn(() => nextChain()),
62
+ } as unknown as Database;
63
+ return { db, captured };
64
+ }
65
+
66
+ /**
67
+ * Does a drizzle condition bind `value`? The condition is an SQL object whose
68
+ * bound parameters sit in nested `queryChunks`; the test only needs to know
69
+ * WHETHER a given ciphertext (or an `is null` chunk) is in it.
70
+ */
71
+ function binds(cond: unknown, value: string): boolean {
72
+ const seen = new Set<unknown>();
73
+ const walk = (node: unknown): boolean => {
74
+ if (node === value) return true;
75
+ if (Array.isArray(node)) return node.some(walk);
76
+ if (node === null || typeof node !== 'object' || seen.has(node)) return false;
77
+ seen.add(node);
78
+ return Object.values(node as Record<string, unknown>).some(walk);
79
+ };
80
+ return walk(cond);
81
+ }
82
+
83
+ /**
84
+ * A drizzle stub that answers UPDATEs from their WHERE clause: an update lands
85
+ * only when its guard binds the row's CURRENT ciphertext. `reads` are what the
86
+ * selects answer in order; `current` is the row as it is when a write arrives,
87
+ * or null when there is no row (a fresh key).
88
+ */
89
+ function whereAwareDb(input: { reads: ReturnType<typeof row>[]; current: ReturnType<typeof row> | null }) {
90
+ const reads = [...input.reads];
91
+ const captured: { values: Record<string, unknown>[]; set: Record<string, unknown>[] } = { values: [], set: [] };
92
+ const updates: { landed: boolean }[] = [];
93
+ const select = () => {
94
+ const next = reads.shift() ?? input.current;
95
+ const result = next ? [next] : [];
96
+ const chain: Record<string, unknown> = {};
97
+ chain.from = () => chain;
98
+ chain.where = () => chain;
99
+ chain.limit = () => chain;
100
+ chain.then = (onF: (v: unknown) => unknown, onR: (e: unknown) => unknown) => Promise.resolve(result).then(onF, onR);
101
+ return chain;
102
+ };
103
+ const update = () => {
104
+ const chain: Record<string, unknown> = {};
105
+ let landed = false;
106
+ chain.set = (v: Record<string, unknown>) => {
107
+ captured.set.push(v);
108
+ return chain;
109
+ };
110
+ chain.where = (cond: unknown) => {
111
+ landed = input.current !== null && binds(cond, input.current.valueEncrypted);
112
+ updates.push({ landed });
113
+ return chain;
114
+ };
115
+ chain.returning = () => Promise.resolve(landed && input.current ? [{ id: input.current.id }] : []);
116
+ return chain;
117
+ };
118
+ const deletes: { landed: boolean }[] = [];
119
+ const del = () => {
120
+ const chain: Record<string, unknown> = {};
121
+ let landed = false;
122
+ chain.where = (cond: unknown) => {
123
+ landed = input.current !== null && binds(cond, input.current.valueEncrypted);
124
+ deletes.push({ landed });
125
+ return chain;
126
+ };
127
+ chain.returning = () =>
128
+ Promise.resolve(landed && input.current ? [{ id: input.current.id, userId: input.current.userId, kind: input.current.kind }] : []);
129
+ return chain;
130
+ };
131
+ // An upsert with no row to conflict with INSERTS, guard or no guard, as the
132
+ // database does; with a row there, it lands only when its `setWhere` guard
133
+ // binds the row's CURRENT ciphertext (or carries no guard at all), and a
134
+ // miss answers no row, as the database does when the conflict update's
135
+ // WHERE is false.
136
+ const inserts: { landed: boolean }[] = [];
137
+ const insert = () => {
138
+ const chain: Record<string, unknown> = {};
139
+ let landed = true;
140
+ let written: Record<string, unknown> | undefined;
141
+ chain.values = (v: Record<string, unknown>) => {
142
+ captured.values.push(v);
143
+ written = v;
144
+ return chain;
145
+ };
146
+ chain.onConflictDoUpdate = (cfg: { setWhere?: unknown }) => {
147
+ landed = input.current === null || cfg.setWhere === undefined || binds(cfg.setWhere, input.current.valueEncrypted);
148
+ inserts.push({ landed });
149
+ return chain;
150
+ };
151
+ chain.returning = () => Promise.resolve(landed ? [input.current ?? { id: 'fresh-row', ...written }] : []);
152
+ return chain;
153
+ };
154
+ const db = {
155
+ select: vi.fn(select),
156
+ update: vi.fn(update),
157
+ insert: vi.fn(insert),
158
+ delete: vi.fn(del),
159
+ } as unknown as Database;
160
+ return { db, captured, updates, deletes, inserts };
161
+ }
162
+
163
+ function row(blob: Record<string, unknown>, meta: Record<string, unknown> = MANAGED_META, userId: string | null = USER) {
164
+ return {
165
+ id: 'row-1',
166
+ userId,
167
+ key: KEY,
168
+ kind: 'oauth',
169
+ label: null,
170
+ valueEncrypted: crypto.encrypt(JSON.stringify(blob)),
171
+ oauthMeta: meta,
172
+ createdAt: new Date(),
173
+ updatedAt: new Date(),
174
+ };
175
+ }
176
+
177
+ const decryptSet = (captured: { set: Record<string, unknown>[] }, i = 0) =>
178
+ JSON.parse(crypto.decrypt(captured.set[i].valueEncrypted as string)) as Record<string, unknown>;
179
+
180
+ describe('a managed sign-in: begin and the handoff', () => {
181
+ it('begin writes a row that expects the nonce, keeping tokens a managed row already held', async () => {
182
+ const existing = row({ tokens: { access_token: 'old', refresh_token: 'r-old' } });
183
+ const { db, captured } = makeFakeDb([[existing], [{ id: 'row-1' }]]);
184
+ const vault = new DbSecretsVaultService(db, ENC_KEY);
185
+ await vault.beginManagedConnection({ userId: USER, key: KEY, provider: MANAGED_META, nonce: NONCE });
186
+ const blob = decryptSet(captured);
187
+ expect(blob.pendingNonce).toBe(NONCE);
188
+ expect(blob.tokens).toEqual({ access_token: 'old', refresh_token: 'r-old' });
189
+ expect(captured.set[0].oauthMeta).toMatchObject({ managed: { connectorId: 'microsoft365', scopeSet: 'read' } });
190
+ });
191
+
192
+ it('begin never overwrites tokens a concurrent refresh rotated: the guard misses the stale ciphertext, the re-read lands', async () => {
193
+ const stale = row({ tokens: { access_token: 'old', refresh_token: 'r-old' } });
194
+ const rotated = row({ tokens: { access_token: 'new', refresh_token: 'r-new' } });
195
+ // The row as the first read sees it, and as it IS by the time the write
196
+ // arrives. An update lands only when its guard binds the current
197
+ // ciphertext — an unguarded write, or one guarded on the stale read,
198
+ // fails this test.
199
+ const { db, captured, updates } = whereAwareDb({ reads: [stale, rotated], current: rotated });
200
+ const vault = new DbSecretsVaultService(db, ENC_KEY);
201
+ await vault.beginManagedConnection({ userId: USER, key: KEY, provider: MANAGED_META, nonce: NONCE });
202
+ expect(updates.map((u) => u.landed)).toEqual([false, true]);
203
+ const landed = decryptSet(captured, 1);
204
+ expect(landed.tokens).toEqual({ access_token: 'new', refresh_token: 'r-new' });
205
+ expect(landed.pendingNonce).toBe(NONCE);
206
+ });
207
+
208
+ it('begin keeps tokens only for the SAME connection: another scope set’s token is revoked, not carried over', async () => {
209
+ const otherSet = row({ tokens: { access_token: 'draft-token' } }, { ...MANAGED_META, managed: { connectorId: 'microsoft365', scopeSet: 'read_draft' } });
210
+ const revoker: ManagedTokenRevoker = { revoke: vi.fn(async () => undefined) };
211
+ const { db, captured } = makeFakeDb([[otherSet], [{ id: 'row-1' }]]);
212
+ const vault = new DbSecretsVaultService(db, ENC_KEY);
213
+ vault.setManagedTokenHandlers({ revoker });
214
+ const changed: (string | null)[] = [];
215
+ vault.onMutation((u) => changed.push(u));
216
+ await vault.beginManagedConnection({ userId: USER, key: KEY, provider: MANAGED_META, nonce: NONCE });
217
+ expect(decryptSet(captured).tokens).toBeUndefined();
218
+ expect(captured.set[0].oauthMeta).not.toHaveProperty('account');
219
+ expect(revoker.revoke).toHaveBeenCalledWith({ connectorId: 'microsoft365', scopeSet: 'read_draft' }, { access_token: 'draft-token', refresh_token: undefined });
220
+ // Something else was replaced: caches are told.
221
+ expect(changed).toEqual([USER]);
222
+ });
223
+
224
+ it('begin survives losing the insert race: the next pass updates the row the other start made', async () => {
225
+ const theirs = row({ pendingNonce: 'their-nonce-their-nonce' });
226
+ // No row; our insert collides; the re-read finds theirs; the guarded update lands.
227
+ const { db, captured } = makeFakeDb([[], [theirs], [{ id: 'row-1' }]]);
228
+ (db as unknown as { insert: ReturnType<typeof vi.fn> }).insert.mockImplementationOnce(() => ({
229
+ values: () => Promise.reject(Object.assign(new Error('duplicate key value violates unique constraint'), { code: '23505' })),
230
+ }));
231
+ const vault = new DbSecretsVaultService(db, ENC_KEY);
232
+ await vault.beginManagedConnection({ userId: USER, key: KEY, provider: MANAGED_META, nonce: NONCE });
233
+ expect(decryptSet(captured).pendingNonce).toBe(NONCE);
234
+ });
235
+
236
+ it('begin gives up after repeated misses, leaving the row as it is', async () => {
237
+ const stored = row({ tokens: { access_token: 'at' } });
238
+ const { db } = makeFakeDb([[stored], [], [stored], [], [stored], []]);
239
+ const vault = new DbSecretsVaultService(db, ENC_KEY);
240
+ await expect(
241
+ vault.beginManagedConnection({ userId: USER, key: KEY, provider: MANAGED_META, nonce: NONCE }),
242
+ ).rejects.toBeInstanceOf(SecretOAuthError);
243
+ expect((db as unknown as { insert: ReturnType<typeof vi.fn> }).insert).not.toHaveBeenCalled();
244
+ });
245
+
246
+ it('begin inserts a fresh row when there is none, and starts clean over a row of another kind', async () => {
247
+ const stale = { ...row({ clientSecret: 'secret-of-an-own-app' }, { ...MANAGED_META, managed: undefined, clientId: 'own' }) };
248
+ const { db, captured } = makeFakeDb([[stale], [{ id: 'row-1' }]]);
249
+ const vault = new DbSecretsVaultService(db, ENC_KEY);
250
+ await vault.beginManagedConnection({ userId: USER, key: KEY, provider: MANAGED_META, nonce: NONCE });
251
+ const blob = decryptSet(captured);
252
+ expect(blob.clientSecret).toBeUndefined();
253
+ expect(blob.tokens).toBeUndefined();
254
+ expect(blob.pendingNonce).toBe(NONCE);
255
+
256
+ const fresh = makeFakeDb([[], []]);
257
+ await new DbSecretsVaultService(fresh.db, ENC_KEY).beginManagedConnection({ userId: null, key: KEY, provider: MANAGED_META, nonce: NONCE });
258
+ expect(fresh.captured.values[0]).toMatchObject({ userId: null, key: KEY, kind: 'oauth' });
259
+ });
260
+
261
+ it('the handoff lands the tokens, clears the nonce and remembers the account', async () => {
262
+ const waiting = row({ pendingNonce: NONCE });
263
+ const { db, captured } = makeFakeDb([[waiting], [{ id: 'row-1' }]]);
264
+ const vault = new DbSecretsVaultService(db, ENC_KEY);
265
+ const changed: (string | null)[] = [];
266
+ vault.onMutation((u) => changed.push(u));
267
+ await vault.completeManagedConnection({
268
+ userId: USER,
269
+ key: KEY,
270
+ nonce: NONCE,
271
+ tokens: { access_token: 'at', refresh_token: 'rt', expires_at: 123, scope: 'Mail.Read' },
272
+ account: 'alice@example.com',
273
+ });
274
+ const blob = decryptSet(captured);
275
+ expect(blob).toEqual({ tokens: { access_token: 'at', refresh_token: 'rt', expires_at: 123, scope: 'Mail.Read' } });
276
+ expect(captured.set[0].oauthMeta).toMatchObject({ account: 'alice@example.com' });
277
+ expect(changed).toEqual([USER]);
278
+ });
279
+
280
+ it('a handoff that names no scope is stored as covering what the sign-in asked for, not nothing', async () => {
281
+ // Stored as unknown, the grant would cover no declared scope: the tool
282
+ // hidden by the pre-call check, the card on "Needs reconnect", and every
283
+ // reconnect landing the same way. The code exchange falls back to the
284
+ // requested scopes; the handoff does the same from the row's declaration.
285
+ const waiting = row({ pendingNonce: NONCE }, { ...MANAGED_META, scopes: ['Mail.Read', 'offline_access'] });
286
+ const { db, captured } = makeFakeDb([[waiting], [{ id: 'row-1' }]]);
287
+ await new DbSecretsVaultService(db, ENC_KEY).completeManagedConnection({
288
+ userId: USER,
289
+ key: KEY,
290
+ nonce: NONCE,
291
+ tokens: { access_token: 'at', refresh_token: 'rt' },
292
+ });
293
+ expect(decryptSet(captured).tokens).toMatchObject({ access_token: 'at', scope: 'Mail.Read offline_access' });
294
+ });
295
+
296
+ it('a handoff whose scope is explicitly empty is an empty grant, not a missing one', async () => {
297
+ const waiting = row({ pendingNonce: NONCE }, { ...MANAGED_META, scopes: ['Mail.Read'] });
298
+ const { db, captured } = makeFakeDb([[waiting], [{ id: 'row-1' }]]);
299
+ await new DbSecretsVaultService(db, ENC_KEY).completeManagedConnection({
300
+ userId: USER,
301
+ key: KEY,
302
+ nonce: NONCE,
303
+ tokens: { access_token: 'at', scope: '' },
304
+ });
305
+ expect((decryptSet(captured).tokens as { scope?: string }).scope).toBe('');
306
+ });
307
+
308
+ it('refuses a handoff whose nonce is wrong, and a second handoff for a nonce already used', async () => {
309
+ const waiting = row({ pendingNonce: NONCE });
310
+ const wrong = new DbSecretsVaultService(makeFakeDb([[waiting]]).db, ENC_KEY);
311
+ await expect(
312
+ wrong.completeManagedConnection({ userId: USER, key: KEY, nonce: 'other-nonce-other-nonce', tokens: { access_token: 'x' } }),
313
+ ).rejects.toBeInstanceOf(SecretOAuthError);
314
+
315
+ // After a handoff landed, the row holds tokens and no nonce: the same
316
+ // handoff again finds nothing to match.
317
+ const landed = row({ tokens: { access_token: 'at' } });
318
+ const again = new DbSecretsVaultService(makeFakeDb([[landed]]).db, ENC_KEY);
319
+ await expect(
320
+ again.completeManagedConnection({ userId: USER, key: KEY, nonce: NONCE, tokens: { access_token: 'x' } }),
321
+ ).rejects.toBeInstanceOf(SecretOAuthError);
322
+ });
323
+
324
+ it('refuses a handoff for a row that is not a managed sign-in, in the same words', async () => {
325
+ const ordinary = row({ pendingNonce: NONCE }, { ...MANAGED_META, managed: undefined, clientId: 'own' });
326
+ const vault = new DbSecretsVaultService(makeFakeDb([[ordinary]]).db, ENC_KEY);
327
+ await expect(
328
+ vault.completeManagedConnection({ userId: USER, key: KEY, nonce: NONCE, tokens: { access_token: 'x' } }),
329
+ ).rejects.toThrow('No managed sign-in is waiting for this handoff');
330
+ });
331
+ });
332
+
333
+ describe('replacing a managed row is guarded on what its revoke read', () => {
334
+ const revoker = (): ManagedTokenRevoker => ({ revoke: vi.fn(async () => undefined) });
335
+ // Every read sees the row as it was; the row IS something else when the
336
+ // write arrives (a refresh rotated its tokens, a handoff landed).
337
+ const asRead = row({ tokens: { access_token: 'at-1', refresh_token: 'rt-1' } });
338
+ const asItIs = row({ tokens: { access_token: 'at-2', refresh_token: 'rt-2' } });
339
+
340
+ it('a write that would overwrite tokens rotated since the revoke misses, re-reads, revokes those too, and lands on the row as it is', async () => {
341
+ const r = revoker();
342
+ const db = whereAwareDb({ reads: [asRead, asItIs], current: asItIs });
343
+ const vault = new DbSecretsVaultService(db.db, ENC_KEY);
344
+ vault.setManagedTokenHandlers({ revoker: r });
345
+ await vault.putStatic({ userId: USER, key: KEY, value: 'typed-token' });
346
+ // The first pass revoked the tokens it read and missed; the second read
347
+ // the rotated ones, revoked them, and its guarded write landed.
348
+ expect(db.inserts.map((i) => i.landed)).toEqual([false, true]);
349
+ expect(vi.mocked(r.revoke).mock.calls.map((c) => c[1].access_token)).toEqual(['at-1', 'at-2']);
350
+ });
351
+
352
+ it('a row that keeps changing under the revoke is left as it is, and the caller is told', async () => {
353
+ const r = revoker();
354
+ const db = whereAwareDb({ reads: [asRead, asRead, asRead, asRead], current: asItIs });
355
+ const vault = new DbSecretsVaultService(db.db, ENC_KEY);
356
+ vault.setManagedTokenHandlers({ revoker: r });
357
+ await expect(vault.createOAuth({ userId: USER, key: KEY, provider: { ...MANAGED_META, managed: undefined, clientId: 'own-app' } })).rejects.toThrow(
358
+ /kept changing while being replaced/,
359
+ );
360
+ expect(db.inserts.map((i) => i.landed)).toEqual([false, false, false]);
361
+ });
362
+
363
+ it('a fresh key with no row to conflict with is inserted under the guard, with nothing to revoke', async () => {
364
+ const r = revoker();
365
+ const db = whereAwareDb({ reads: [], current: null });
366
+ const vault = new DbSecretsVaultService(db.db, ENC_KEY);
367
+ vault.setManagedTokenHandlers({ revoker: r });
368
+ const summary = await vault.putStatic({ userId: USER, key: KEY, value: 'typed-token' });
369
+ expect(summary.key).toBe(KEY);
370
+ expect(db.inserts).toEqual([{ landed: true }]);
371
+ expect(r.revoke).not.toHaveBeenCalled();
372
+ });
373
+
374
+ it('with no revoker wired the replacement is one plain write, as before managed sign-ins', async () => {
375
+ const db = whereAwareDb({ reads: [], current: asItIs });
376
+ await new DbSecretsVaultService(db.db, ENC_KEY).putStatic({ userId: USER, key: KEY, value: 'typed-token' });
377
+ expect(db.db.select).not.toHaveBeenCalled();
378
+ expect(db.inserts).toEqual([{ landed: true }]);
379
+ });
380
+ });
381
+
382
+ describe('a handoff survives a refresh that rotates the row under it', () => {
383
+ it('a guard miss with the nonce still on the row re-reads and lands on the row as it is now', async () => {
384
+ // Begun, with tokens of the same connection kept; a refresh rotated them
385
+ // while the broker was doing the round-trip, and carried the nonce across.
386
+ const asRead = row({ tokens: { access_token: 'at-1', refresh_token: 'rt-1' }, pendingNonce: NONCE });
387
+ const rotated = row({ tokens: { access_token: 'at-2', refresh_token: 'rt-2' }, pendingNonce: NONCE });
388
+ const db = whereAwareDb({ reads: [asRead, rotated], current: rotated });
389
+ const vault = new DbSecretsVaultService(db.db, ENC_KEY);
390
+ await vault.completeManagedConnection({ userId: USER, key: KEY, nonce: NONCE, tokens: { access_token: 'delivered' } });
391
+ expect(db.updates.map((u) => u.landed)).toEqual([false, true]);
392
+ const blob = JSON.parse(crypto.decrypt(db.captured.set[1].valueEncrypted as string)) as { tokens?: { access_token: string }; pendingNonce?: string };
393
+ expect(blob.tokens?.access_token).toBe('delivered');
394
+ expect(blob.pendingNonce).toBeUndefined();
395
+ });
396
+
397
+ it('a guard miss whose re-read finds the nonce gone is a handoff that already landed: refused', async () => {
398
+ const asRead = row({ tokens: { access_token: 'at-1' }, pendingNonce: NONCE });
399
+ const landedAlready = row({ tokens: { access_token: 'delivered' } });
400
+ const db = whereAwareDb({ reads: [asRead, landedAlready], current: landedAlready });
401
+ const vault = new DbSecretsVaultService(db.db, ENC_KEY);
402
+ await expect(
403
+ vault.completeManagedConnection({ userId: USER, key: KEY, nonce: NONCE, tokens: { access_token: 'again' } }),
404
+ ).rejects.toThrow('No managed sign-in is waiting for this handoff');
405
+ expect(db.updates.map((u) => u.landed)).toEqual([false]);
406
+ });
407
+ });
408
+
409
+ describe('an account erasure removes the managed rows inside its own transaction as well', () => {
410
+ it('locks, revokes and deletes through the transaction it is handed, touching the vault’s own connection for nothing', async () => {
411
+ const managed = row({ tokens: { access_token: 'at', refresh_token: 'rt' } });
412
+ const revoker: ManagedTokenRevoker = { revoke: vi.fn(async () => undefined) };
413
+ const own = makeFakeDb([]);
414
+ const vault = new DbSecretsVaultService(own.db, ENC_KEY);
415
+ vault.setManagedTokenHandlers({ revoker });
416
+ // The transaction: one locked read of the user's oauth rows, one delete.
417
+ const tx = makeFakeDb([[managed], undefined]);
418
+ await managedSignInErasure(vault).inTransaction(tx.db, { userId: USER });
419
+ expect(revoker.revoke).toHaveBeenCalledWith({ connectorId: 'microsoft365', scopeSet: 'read' }, { access_token: 'at', refresh_token: 'rt' });
420
+ expect(tx.db.select).toHaveBeenCalledTimes(1);
421
+ // Locked, not merely read: the lock is what makes a handoff arriving now wait.
422
+ expect(tx.captured.for).toEqual(['update']);
423
+ expect(tx.db.delete).toHaveBeenCalledTimes(1);
424
+ expect(own.db.select).not.toHaveBeenCalled();
425
+ expect(own.db.delete).not.toHaveBeenCalled();
426
+ });
427
+
428
+ it('a transaction that finds no managed row deletes nothing', async () => {
429
+ const vault = new DbSecretsVaultService(makeFakeDb([]).db, ENC_KEY);
430
+ vault.setManagedTokenHandlers({ revoker: { revoke: vi.fn(async () => undefined) } });
431
+ const tx = makeFakeDb([[row({ clientSecret: 's' }, { ...MANAGED_META, managed: undefined, clientId: 'own' })]]);
432
+ await managedSignInErasure(vault).inTransaction(tx.db, { userId: USER });
433
+ expect(tx.db.delete).not.toHaveBeenCalled();
434
+ });
435
+ });
436
+
437
+ describe('a managed sign-in: refresh through the broker', () => {
438
+ const expired = { access_token: 'stale', refresh_token: 'rt', expires_at: 1_000 };
439
+ const scopeOf = async () => 'user' as const;
440
+
441
+ it('a broker that never answers is an outage: the stale token is served once the bound passes, and nothing is wiped', async () => {
442
+ vi.useFakeTimers();
443
+ try {
444
+ const stored = row({ tokens: expired });
445
+ const { db, captured } = makeFakeDb([[stored]]);
446
+ const refresher: ManagedTokenRefresher = { refresh: vi.fn(() => new Promise(() => {})) };
447
+ const vault = new DbSecretsVaultService(db, ENC_KEY, () => 5_000_000, scopeOf);
448
+ vault.setManagedTokenHandlers({ refresher });
449
+ const resolving = vault.resolve(USER, KEY);
450
+ await vi.advanceTimersByTimeAsync(15_000);
451
+ expect(await resolving).toBe('stale');
452
+ expect(captured.set).toEqual([]);
453
+ } finally {
454
+ vi.useRealTimers();
455
+ }
456
+ });
457
+
458
+ it('resolve renews an expired token through the refresher and stores what it delivered', async () => {
459
+ const stored = row({ tokens: expired });
460
+ const { db, captured } = makeFakeDb([[stored], [{ id: 'row-1' }]]);
461
+ const refresher: ManagedTokenRefresher = { refresh: vi.fn(async () => ({ access_token: 'fresh', expires_at: 9_000_000 })) };
462
+ const vault = new DbSecretsVaultService(db, ENC_KEY, () => 5_000_000, scopeOf);
463
+ vault.setManagedTokenHandlers({ refresher });
464
+ expect(await vault.resolve(USER, KEY)).toBe('fresh');
465
+ expect(refresher.refresh).toHaveBeenCalledWith({ connectorId: 'microsoft365', scopeSet: 'read' }, 'rt');
466
+ const blob = decryptSet(captured);
467
+ // The refresh token is kept when the broker sends none back.
468
+ expect(blob.tokens).toMatchObject({ access_token: 'fresh', refresh_token: 'rt' });
469
+ });
470
+
471
+ it('a 401 from the broker is a dead grant: the tokens are wiped, the sign-in reads as not connected', async () => {
472
+ const stored = row({ tokens: expired });
473
+ const { db, captured } = makeFakeDb([[stored], [{ id: 'row-1' }]]);
474
+ const refresher: ManagedTokenRefresher = {
475
+ refresh: vi.fn(async () => {
476
+ throw new SecretOAuthError('The connector broker answered HTTP 401', 401);
477
+ }),
478
+ };
479
+ const vault = new DbSecretsVaultService(db, ENC_KEY, () => 5_000_000, scopeOf);
480
+ vault.setManagedTokenHandlers({ refresher });
481
+ expect(await vault.resolve(USER, KEY)).toBeNull();
482
+ expect(decryptSet(captured).tokens).toBeUndefined();
483
+ });
484
+
485
+ it('a reconnect waiting for its handoff survives a refresh and a wipe in between: the nonce stays on the row', async () => {
486
+ // `beginManagedConnection` keeps the tokens a managed row holds beside the
487
+ // nonce it mints, so a refresh (or a dead grant's wipe) can land on the row
488
+ // while the person is still with the provider. The handoff that follows
489
+ // must still find its nonce.
490
+ const waitingWithTokens = row({ tokens: expired, pendingNonce: NONCE });
491
+ const refresher: ManagedTokenRefresher = { refresh: vi.fn(async () => ({ access_token: 'fresh', expires_at: 9_000_000 })) };
492
+ const refreshedDb = makeFakeDb([[waitingWithTokens], [{ id: 'row-1' }]]);
493
+ const v1 = new DbSecretsVaultService(refreshedDb.db, ENC_KEY, () => 5_000_000, scopeOf);
494
+ v1.setManagedTokenHandlers({ refresher });
495
+ expect(await v1.resolve(USER, KEY)).toBe('fresh');
496
+ expect(decryptSet(refreshedDb.captured)).toMatchObject({ pendingNonce: NONCE, tokens: { access_token: 'fresh' } });
497
+
498
+ const dead: ManagedTokenRefresher = {
499
+ refresh: vi.fn(async () => {
500
+ throw new SecretOAuthError('The connector broker answered HTTP 401', 401);
501
+ }),
502
+ };
503
+ const wipedDb = makeFakeDb([[waitingWithTokens], [{ id: 'row-1' }]]);
504
+ const v2 = new DbSecretsVaultService(wipedDb.db, ENC_KEY, () => 5_000_000, scopeOf);
505
+ v2.setManagedTokenHandlers({ refresher: dead });
506
+ expect(await v2.resolve(USER, KEY)).toBeNull();
507
+ const wiped = decryptSet(wipedDb.captured);
508
+ expect(wiped.tokens).toBeUndefined();
509
+ expect(wiped.pendingNonce).toBe(NONCE);
510
+ });
511
+
512
+ it('with no broker wired, an expired token is served as it is and nothing is written', async () => {
513
+ const stored = row({ tokens: expired });
514
+ const { db, captured } = makeFakeDb([[stored]]);
515
+ const vault = new DbSecretsVaultService(db, ENC_KEY, () => 5_000_000, scopeOf);
516
+ expect(await vault.resolve(USER, KEY)).toBe('stale');
517
+ expect(captured.set).toHaveLength(0);
518
+ });
519
+
520
+ it('forceRefresh repairs the SHARED row of a workspace connection: it reads user_id IS NULL, not the caller’s row', async () => {
521
+ const shared = row({ tokens: expired }, MANAGED_META, null);
522
+ const { db, captured } = makeFakeDb([[shared], [{ id: 'row-1' }]]);
523
+ const refresher: ManagedTokenRefresher = { refresh: vi.fn(async () => ({ access_token: 'fresh' })) };
524
+ const vault = new DbSecretsVaultService(db, ENC_KEY, () => 5_000_000, async () => 'admin');
525
+ vault.setManagedTokenHandlers({ refresher });
526
+ expect(await vault.forceRefresh(USER, KEY)).toBe('refreshed');
527
+ // The read's predicate: the shared tier (an `is null` on user_id), and
528
+ // never the caller's id.
529
+ const [readWhere] = captured.where;
530
+ expect(binds(readWhere, ' is null')).toBe(true);
531
+ expect(binds(readWhere, USER)).toBe(false);
532
+ // And what was persisted is the refreshed set, on the row read.
533
+ expect(decryptSet(captured).tokens).toMatchObject({ access_token: 'fresh', refresh_token: 'rt' });
534
+ });
535
+ });
536
+
537
+ describe('a managed sign-in: revoked with the provider before it is deleted', () => {
538
+ it('a delete is guarded on the ciphertext the revoke read: tokens a handoff landed in between are not deleted unrevoked', async () => {
539
+ const before = row({ tokens: { access_token: 'at-1', refresh_token: 'rt-1' } });
540
+ const after = row({ tokens: { access_token: 'at-2', refresh_token: 'rt-2' } });
541
+ const revoked: string[] = [];
542
+ const revoker: ManagedTokenRevoker = {
543
+ revoke: vi.fn(async (_ref, tokens) => {
544
+ revoked.push(tokens.access_token);
545
+ }),
546
+ };
547
+ // The row as the first read sees it, and as it IS when the delete
548
+ // arrives: a delete lands only when its guard binds the current
549
+ // ciphertext, so an unguarded or stale-guarded delete fails this test.
550
+ const { db, deletes } = whereAwareDb({ reads: [before, after], current: after });
551
+ const vault = new DbSecretsVaultService(db, ENC_KEY);
552
+ vault.setManagedTokenHandlers({ revoker });
553
+ await vault.removeUserByKey(USER, KEY);
554
+ expect(revoked).toEqual(['at-1', 'at-2']);
555
+ expect(deletes.map((d) => d.landed)).toEqual([false, true]);
556
+ });
557
+
558
+ it('removeUserByKey tells the revoker first, then deletes, and deletes even when revoking fails', async () => {
559
+ const stored = row({ tokens: { access_token: 'at', refresh_token: 'rt' } });
560
+ const order: string[] = [];
561
+ const revoker: ManagedTokenRevoker = {
562
+ revoke: vi.fn(async () => {
563
+ order.push('revoke');
564
+ }),
565
+ };
566
+ const { db } = makeFakeDb([[stored], [{ id: 'row-1' }]]);
567
+ (db as unknown as { delete: ReturnType<typeof vi.fn> }).delete.mockImplementation(() => {
568
+ order.push('delete');
569
+ const chain: Record<string, unknown> = {};
570
+ chain.where = () => chain;
571
+ chain.returning = () => Promise.resolve([{ id: 'row-1' }]);
572
+ return chain;
573
+ });
574
+ const vault = new DbSecretsVaultService(db, ENC_KEY);
575
+ vault.setManagedTokenHandlers({ revoker });
576
+ await vault.removeUserByKey(USER, KEY);
577
+ expect(order).toEqual(['revoke', 'delete']);
578
+ expect(revoker.revoke).toHaveBeenCalledWith({ connectorId: 'microsoft365', scopeSet: 'read' }, { access_token: 'at', refresh_token: 'rt' });
579
+
580
+ const failing: ManagedTokenRevoker = {
581
+ revoke: vi.fn(async () => {
582
+ throw new Error('provider down');
583
+ }),
584
+ };
585
+ const second = makeFakeDb([[stored], [{ id: 'row-1' }]]);
586
+ const v2 = new DbSecretsVaultService(second.db, ENC_KEY);
587
+ v2.setManagedTokenHandlers({ revoker: failing });
588
+ await expect(v2.removeUserByKey(USER, KEY)).resolves.toBeUndefined();
589
+ });
590
+
591
+ it('removeManagedRowsOfUser revokes and deletes only the managed rows, reporting how many', async () => {
592
+ const managed = row({ tokens: { access_token: 'at' } });
593
+ const ordinary = { ...row({ tokens: { access_token: 'o' } }, { ...MANAGED_META, managed: undefined, clientId: 'own' }), id: 'row-2', key: 'other_TOKEN' };
594
+ const revoker: ManagedTokenRevoker = { revoke: vi.fn(async () => undefined) };
595
+ // The user's rows; then the managed one re-read for the guarded delete; then the delete's answer.
596
+ const { db } = makeFakeDb([[managed, ordinary], [managed], [{ id: 'row-1', userId: USER, kind: 'oauth' }]]);
597
+ const vault = new DbSecretsVaultService(db, ENC_KEY);
598
+ vault.setManagedTokenHandlers({ revoker });
599
+ expect(await vault.removeManagedRowsOfUser(USER)).toBe(1);
600
+ expect(revoker.revoke).toHaveBeenCalledTimes(1);
601
+ });
602
+
603
+ it('a bulk delete waits one revocation bound for every hung revoker, then deletes', async () => {
604
+ vi.useFakeTimers();
605
+ try {
606
+ const first = row({ tokens: { access_token: 'at-1' } });
607
+ const second = { ...row({ tokens: { access_token: 'at-2' } }), id: 'row-2', key: 'google_ACCESS_TOKEN' };
608
+ // A revoker that never settles: the bound is the only way out.
609
+ const revoker: ManagedTokenRevoker = { revoke: vi.fn(() => new Promise<void>(() => {})) };
610
+ // The user's rows; the same two re-read for the guarded delete; one answer per delete.
611
+ const { db } = makeFakeDb([
612
+ [first, second],
613
+ [first, second],
614
+ [{ id: 'row-1', userId: USER, kind: 'oauth' }],
615
+ [{ id: 'row-2', userId: USER, kind: 'oauth' }],
616
+ ]);
617
+ const vault = new DbSecretsVaultService(db, ENC_KEY);
618
+ vault.setManagedTokenHandlers({ revoker });
619
+ let settled: number | null = null;
620
+ const removal = vault.removeManagedRowsOfUser(USER).then((n) => (settled = n));
621
+ await vi.advanceTimersByTimeAsync(9_999);
622
+ expect(settled).toBeNull();
623
+ // ONE bound (the revocations run together), not one per row: a second
624
+ // row's revocation cannot add a second wait.
625
+ await vi.advanceTimersByTimeAsync(1);
626
+ await removal;
627
+ expect(settled).toBe(2);
628
+ expect(revoker.revoke).toHaveBeenCalledTimes(2);
629
+ } finally {
630
+ vi.useRealTimers();
631
+ }
632
+ });
633
+
634
+ it('a value or an own-app client written over the caller’s managed row revokes its tokens first', async () => {
635
+ // Every replacement of a row goes through the same guard: a typed value
636
+ // and a registered client of the caller's own both drop the tokens the
637
+ // broker delivered, and the provider is told before they go.
638
+ const managed = row({ tokens: { access_token: 'at', refresh_token: 'rt' } });
639
+ const order: string[] = [];
640
+ const revoker: ManagedTokenRevoker = {
641
+ revoke: vi.fn(async () => {
642
+ order.push('revoke');
643
+ }),
644
+ };
645
+ // The upsert records when it runs, so the order against the revoke is
646
+ // what the test asserts, not only that both happened.
647
+ const recordingInsert = (returned: unknown) => () => {
648
+ order.push('write');
649
+ const chain: Record<string, unknown> = {};
650
+ chain.values = () => chain;
651
+ chain.onConflictDoUpdate = () => chain;
652
+ chain.returning = () => Promise.resolve([returned]);
653
+ return chain;
654
+ };
655
+ const typed = makeFakeDb([[managed]]);
656
+ (typed.db as unknown as { insert: ReturnType<typeof vi.fn> }).insert.mockImplementation(recordingInsert({ ...managed, kind: 'static' }));
657
+ const v1 = new DbSecretsVaultService(typed.db, ENC_KEY);
658
+ v1.setManagedTokenHandlers({ revoker });
659
+ await v1.putStatic({ userId: USER, key: KEY, value: 'typed-token' });
660
+ expect(revoker.revoke).toHaveBeenCalledWith({ connectorId: 'microsoft365', scopeSet: 'read' }, { access_token: 'at', refresh_token: 'rt' });
661
+ expect(order).toEqual(['revoke', 'write']);
662
+
663
+ order.length = 0;
664
+ const own = makeFakeDb([[managed]]);
665
+ (own.db as unknown as { insert: ReturnType<typeof vi.fn> }).insert.mockImplementation(recordingInsert(managed));
666
+ const v2 = new DbSecretsVaultService(own.db, ENC_KEY);
667
+ v2.setManagedTokenHandlers({ revoker });
668
+ await v2.createOAuth({ userId: USER, key: KEY, provider: { ...MANAGED_META, managed: undefined, clientId: 'own-app' } });
669
+ expect(order).toEqual(['revoke', 'write']);
670
+ });
671
+
672
+ it('a managed row that keeps changing under the revoke is reported, and an erasure is refused rather than cascading it unrevoked', async () => {
673
+ const revoker: ManagedTokenRevoker = { revoke: vi.fn(async () => undefined) };
674
+ // Every read sees the row as it was; by the time each delete arrives the
675
+ // row IS something else (a refresh rotated its tokens), so the guard on
676
+ // the ciphertext read misses — three times, the bound.
677
+ const asRead = row({ tokens: { access_token: 'at-1', refresh_token: 'rt-1' } });
678
+ const asItIs = row({ tokens: { access_token: 'at-2', refresh_token: 'rt-2' } });
679
+ const unsettled = () => whereAwareDb({ reads: [asRead, asRead, asRead, asRead], current: asItIs });
680
+ const first = unsettled();
681
+ const vault = new DbSecretsVaultService(first.db, ENC_KEY);
682
+ vault.setManagedTokenHandlers({ revoker });
683
+ await expect(vault.removeManagedRowsOfUser(USER)).rejects.toThrow(/kept changing while being revoked/);
684
+ expect(first.deletes.map((d) => d.landed)).toEqual([false, false, false]);
685
+
686
+ const second = unsettled();
687
+ const v2 = new DbSecretsVaultService(second.db, ENC_KEY);
688
+ v2.setManagedTokenHandlers({ revoker });
689
+ await expect(managedSignInErasure(v2).before({ userId: USER })).rejects.toThrow(/kept changing/);
690
+ expect(second.deletes.map((d) => d.landed)).toEqual([false, false, false]);
691
+ });
692
+
693
+ it('erasing the account revokes its managed sign-ins before the rows cascade away with the user', async () => {
694
+ // Erasure deletes the user row and the vault rows go with it (ON DELETE
695
+ // CASCADE); without this step an account erased while still switched on
696
+ // would leave the grant live at the provider.
697
+ const managed = row({ tokens: { access_token: 'at', refresh_token: 'rt' } });
698
+ const revoker: ManagedTokenRevoker = { revoke: vi.fn(async () => undefined) };
699
+ const { db } = makeFakeDb([[managed], [managed], [{ id: 'row-1', userId: USER, kind: 'oauth' }]]);
700
+ const vault = new DbSecretsVaultService(db, ENC_KEY);
701
+ vault.setManagedTokenHandlers({ revoker });
702
+ await managedSignInErasure(vault).before({ userId: USER });
703
+ expect(revoker.revoke).toHaveBeenCalledWith({ connectorId: 'microsoft365', scopeSet: 'read' }, { access_token: 'at', refresh_token: 'rt' });
704
+ });
705
+ });
706
+
707
+ describe('a managed provider is accepted only where a managed sign-in is opened', () => {
708
+ it('createOAuth still requires a client id, whatever `managed` a caller slips in', async () => {
709
+ const vault = new DbSecretsVaultService(makeFakeDb([]).db, ENC_KEY);
710
+ await expect(vault.createOAuth({ userId: USER, key: 'mine_TOKEN', provider: MANAGED_META })).rejects.toThrow(/clientId is required/);
711
+ });
712
+
713
+ it('an own app replacing a shared managed connection revokes its tokens first', async () => {
714
+ const shared = row({ tokens: { access_token: 'at', refresh_token: 'rt' } }, MANAGED_META, null);
715
+ const revoker: ManagedTokenRevoker = { revoke: vi.fn(async () => undefined) };
716
+ // The row as read before the revoke, then the guarded write answering the row it wrote.
717
+ const { db } = makeFakeDb([[shared], [{ id: 'shared-row' }]]);
718
+ const vault = new DbSecretsVaultService(db, ENC_KEY);
719
+ vault.setManagedTokenHandlers({ revoker });
720
+ await vault.putSharedOAuthClientSecret({
721
+ key: KEY,
722
+ clientSecret: 'own-secret',
723
+ provider: { ...MANAGED_META, managed: undefined, clientId: 'own-app' },
724
+ });
725
+ expect(revoker.revoke).toHaveBeenCalledWith({ connectorId: 'microsoft365', scopeSet: 'read' }, { access_token: 'at', refresh_token: 'rt' });
726
+ });
727
+ });
728
+
729
+ describe('an owner’s own app on a managed sign-in: the start runs from the pinned client', () => {
730
+ /** The shared row an owner pinned: the deployment's own client, no managed mark. */
731
+ const pinned = row({ clientSecret: 'own-secret' }, { ...MANAGED_META, managed: undefined, clientId: 'own-app' }, null);
732
+ /** The declaration as the route hands it over: a managed sign-in names no client of its own. */
733
+ const declared = { authorizationUrl: MANAGED_META.authorizationUrl, tokenUrl: MANAGED_META.tokenUrl, clientId: '', scopes: ['Mail.Read'] };
734
+ const start = (vault: DbSecretsVaultService) =>
735
+ vault.beginToolOAuthByKey({ userId: USER, key: KEY, redirectUri: 'https://hexis.example/api/secrets/oauth/callback', state: 's', provider: declared });
736
+
737
+ it('a declaration without a client id runs with the client id stored on the pinned row', async () => {
738
+ const { db, captured } = makeFakeDb([[pinned], [], [{ id: 'row-1' }]]);
739
+ const vault = new DbSecretsVaultService(db, ENC_KEY);
740
+ const { url } = await start(vault);
741
+ expect(new URL(url).searchParams.get('client_id')).toBe('own-app');
742
+ expect(captured.values[0]).toMatchObject({ userId: USER, key: KEY, kind: 'oauth', oauthMeta: expect.objectContaining({ clientId: 'own-app' }) });
743
+ expect((captured.values[0].oauthMeta as Record<string, unknown>).managed).toBeUndefined();
744
+ });
745
+
746
+ it('starting over the caller’s managed row revokes the broker’s tokens and carries none of them', async () => {
747
+ const theirs = row({ tokens: { access_token: 'at', refresh_token: 'rt' } });
748
+ const revoker: ManagedTokenRevoker = { revoke: vi.fn(async () => undefined) };
749
+ // The shared row, the caller's row for the tokens it may carry, the caller's
750
+ // row again as the guarded replacement reads it, then the write's answer.
751
+ const { db, captured } = makeFakeDb([[pinned], [theirs], [theirs], [{ id: 'row-1' }]]);
752
+ const vault = new DbSecretsVaultService(db, ENC_KEY);
753
+ vault.setManagedTokenHandlers({ revoker });
754
+ await start(vault);
755
+ expect(revoker.revoke).toHaveBeenCalledWith({ connectorId: 'microsoft365', scopeSet: 'read' }, { access_token: 'at', refresh_token: 'rt' });
756
+ const blob = JSON.parse(crypto.decrypt(captured.values[0].valueEncrypted as string)) as { tokens?: unknown; clientSecret?: string };
757
+ expect(blob.tokens).toBeUndefined();
758
+ expect(blob.clientSecret).toBe('own-secret');
759
+ });
760
+ });
761
+
762
+ describe('status of a managed sign-in', () => {
763
+ it('reports the shared row as authorized with its account, and an own app as such', async () => {
764
+ const shared = row({ tokens: { access_token: 'at', scope: 'crm.objects.contacts.read' } }, { ...MANAGED_META, account: 'acme.hubspot.com' }, null);
765
+ const { db } = makeFakeDb([[shared]]);
766
+ const [st] = await new DbSecretsVaultService(db, ENC_KEY).statusFor(USER, [KEY]);
767
+ expect(st).toMatchObject({ adminConfigured: true, adminAuthorized: true, adminAccount: 'acme.hubspot.com', adminBringsOwnApp: false, userConfigured: false });
768
+
769
+ const own = row({ clientSecret: 's' }, { ...MANAGED_META, managed: undefined, clientId: 'own-app' }, null);
770
+ const [st2] = await new DbSecretsVaultService(makeFakeDb([[own]]).db, ENC_KEY).statusFor(USER, [KEY]);
771
+ expect(st2).toMatchObject({ adminBringsOwnApp: true, adminAuthorized: false });
772
+ });
773
+ });