@bevel-software/platform-core-backend 0.33.0 → 0.35.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 (602) hide show
  1. package/agent-guide/access-control.md +146 -289
  2. package/agent-guide/conventions.md +23 -25
  3. package/agent-guide/directory-structure.md +72 -120
  4. package/agent-guide/drafts-and-change-requests.md +92 -0
  5. package/agent-guide/for-git-users.md +47 -0
  6. package/agent-guide/glossary.md +36 -0
  7. package/agent-guide/how-agents-find-things.md +49 -0
  8. package/agent-guide/html-views.md +239 -8
  9. package/agent-guide/new-knowledge-base.md +9 -8
  10. package/agent-guide/skills.md +67 -42
  11. package/agent-guide/start-here.md +65 -0
  12. package/agent-guide/tool-manuals.md +133 -155
  13. package/agent-guide/tools.md +67 -0
  14. package/agent-guide/where-a-new-file-goes.md +45 -28
  15. package/dist/core/catalog-revision.d.ts +12 -1
  16. package/dist/core/catalog-revision.d.ts.map +1 -1
  17. package/dist/core/catalog-revision.js +23 -3
  18. package/dist/core/catalog-revision.js.map +1 -1
  19. package/dist/core/core-ports.d.ts +41 -0
  20. package/dist/core/core-ports.d.ts.map +1 -1
  21. package/dist/core/core-ports.js.map +1 -1
  22. package/dist/core/create-core-server.d.ts.map +1 -1
  23. package/dist/core/create-core-server.js +15 -8
  24. package/dist/core/create-core-server.js.map +1 -1
  25. package/dist/core/create-core-services.d.ts +22 -5
  26. package/dist/core/create-core-services.d.ts.map +1 -1
  27. package/dist/core/create-core-services.js +44 -16
  28. package/dist/core/create-core-services.js.map +1 -1
  29. package/dist/core/lifecycle.d.ts +16 -0
  30. package/dist/core/lifecycle.d.ts.map +1 -1
  31. package/dist/core/lifecycle.js +12 -2
  32. package/dist/core/lifecycle.js.map +1 -1
  33. package/dist/index.d.ts +1 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js.map +1 -1
  36. package/dist/modules/access/access-control.interface.d.ts +39 -9
  37. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  38. package/dist/modules/access/access-control.service.d.ts +15 -3
  39. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  40. package/dist/modules/access/access-control.service.js +239 -75
  41. package/dist/modules/access/access-control.service.js.map +1 -1
  42. package/dist/modules/access/access-mutation.service.d.ts +22 -24
  43. package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
  44. package/dist/modules/access/access-mutation.service.js +48 -51
  45. package/dist/modules/access/access-mutation.service.js.map +1 -1
  46. package/dist/modules/access/access-view.d.ts.map +1 -1
  47. package/dist/modules/access/access-view.js +12 -11
  48. package/dist/modules/access/access-view.js.map +1 -1
  49. package/dist/modules/access/access.routes.d.ts +3 -0
  50. package/dist/modules/access/access.routes.d.ts.map +1 -1
  51. package/dist/modules/access/access.routes.js +58 -33
  52. package/dist/modules/access/access.routes.js.map +1 -1
  53. package/dist/modules/access/groups-admin.routes.d.ts.map +1 -1
  54. package/dist/modules/access/groups-admin.routes.js +4 -2
  55. package/dist/modules/access/groups-admin.routes.js.map +1 -1
  56. package/dist/modules/access/groups-admin.service.d.ts +44 -13
  57. package/dist/modules/access/groups-admin.service.d.ts.map +1 -1
  58. package/dist/modules/access/groups-admin.service.js +80 -38
  59. package/dist/modules/access/groups-admin.service.js.map +1 -1
  60. package/dist/modules/access/groups-edit.d.ts +1 -1
  61. package/dist/modules/access/groups-edit.d.ts.map +1 -1
  62. package/dist/modules/access/groups-edit.js +1 -1
  63. package/dist/modules/access/groups-edit.js.map +1 -1
  64. package/dist/modules/access/index.d.ts +1 -1
  65. package/dist/modules/access/index.d.ts.map +1 -1
  66. package/dist/modules/access/reference-scan.d.ts +116 -24
  67. package/dist/modules/access/reference-scan.d.ts.map +1 -1
  68. package/dist/modules/access/reference-scan.js +305 -44
  69. package/dist/modules/access/reference-scan.js.map +1 -1
  70. package/dist/modules/access/roles-admin.service.d.ts +17 -8
  71. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  72. package/dist/modules/access/roles-admin.service.js +32 -23
  73. package/dist/modules/access/roles-admin.service.js.map +1 -1
  74. package/dist/modules/access-model/access-grammar.d.ts +35 -10
  75. package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
  76. package/dist/modules/access-model/access-grammar.js +60 -20
  77. package/dist/modules/access-model/access-grammar.js.map +1 -1
  78. package/dist/modules/access-model/access-splice.d.ts +22 -18
  79. package/dist/modules/access-model/access-splice.d.ts.map +1 -1
  80. package/dist/modules/access-model/access-splice.js +20 -14
  81. package/dist/modules/access-model/access-splice.js.map +1 -1
  82. package/dist/modules/access-model/group-files.d.ts +1 -0
  83. package/dist/modules/access-model/group-files.d.ts.map +1 -1
  84. package/dist/modules/access-model/group-files.js +5 -1
  85. package/dist/modules/access-model/group-files.js.map +1 -1
  86. package/dist/modules/agent-guide/agent-guide.d.ts +47 -7
  87. package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
  88. package/dist/modules/agent-guide/agent-guide.js +103 -13
  89. package/dist/modules/agent-guide/agent-guide.js.map +1 -1
  90. package/dist/modules/agent-guide/agent-guide.tools.d.ts +6 -2
  91. package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -1
  92. package/dist/modules/agent-guide/agent-guide.tools.js +46 -22
  93. package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -1
  94. package/dist/modules/agent-guide/guide-budget.d.ts +48 -0
  95. package/dist/modules/agent-guide/guide-budget.d.ts.map +1 -0
  96. package/dist/modules/agent-guide/guide-budget.js +67 -0
  97. package/dist/modules/agent-guide/guide-budget.js.map +1 -0
  98. package/dist/modules/agent-guide/index.d.ts +2 -1
  99. package/dist/modules/agent-guide/index.d.ts.map +1 -1
  100. package/dist/modules/agent-guide/index.js +2 -1
  101. package/dist/modules/agent-guide/index.js.map +1 -1
  102. package/dist/modules/agent-instructions/compose.d.ts +3 -1
  103. package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
  104. package/dist/modules/agent-instructions/compose.js +7 -1
  105. package/dist/modules/agent-instructions/compose.js.map +1 -1
  106. package/dist/modules/agent-instructions/shared-file-rules.d.ts +5 -1
  107. package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
  108. package/dist/modules/agent-instructions/shared-file-rules.js +26 -9
  109. package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
  110. package/dist/modules/agent-scope/agent-scope.contract.d.ts +67 -0
  111. package/dist/modules/agent-scope/agent-scope.contract.d.ts.map +1 -0
  112. package/dist/modules/agent-scope/agent-scope.contract.js +27 -0
  113. package/dist/modules/agent-scope/agent-scope.contract.js.map +1 -0
  114. package/dist/modules/agent-scope/connection-scope.d.ts +111 -0
  115. package/dist/modules/agent-scope/connection-scope.d.ts.map +1 -0
  116. package/dist/modules/agent-scope/connection-scope.js +165 -0
  117. package/dist/modules/agent-scope/connection-scope.js.map +1 -0
  118. package/dist/modules/agent-scope/index.d.ts +3 -0
  119. package/dist/modules/agent-scope/index.d.ts.map +1 -0
  120. package/dist/modules/agent-scope/index.js +3 -0
  121. package/dist/modules/agent-scope/index.js.map +1 -0
  122. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
  123. package/dist/modules/audit/agent-audit.service.js +2 -0
  124. package/dist/modules/audit/agent-audit.service.js.map +1 -1
  125. package/dist/modules/audit/audit.contract.d.ts +6 -0
  126. package/dist/modules/audit/audit.contract.d.ts.map +1 -1
  127. package/dist/modules/audit/audit.contract.js.map +1 -1
  128. package/dist/modules/auth/account-admission.d.ts +12 -0
  129. package/dist/modules/auth/account-admission.d.ts.map +1 -1
  130. package/dist/modules/auth/account-admission.js +15 -0
  131. package/dist/modules/auth/account-admission.js.map +1 -1
  132. package/dist/modules/auth/auth.middleware.d.ts +27 -1
  133. package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
  134. package/dist/modules/auth/auth.middleware.js +42 -4
  135. package/dist/modules/auth/auth.middleware.js.map +1 -1
  136. package/dist/modules/auth/auth.routes.d.ts +13 -9
  137. package/dist/modules/auth/auth.routes.d.ts.map +1 -1
  138. package/dist/modules/auth/auth.routes.js +35 -29
  139. package/dist/modules/auth/auth.routes.js.map +1 -1
  140. package/dist/modules/auth/auth.service.d.ts +55 -6
  141. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  142. package/dist/modules/auth/auth.service.js +128 -34
  143. package/dist/modules/auth/auth.service.js.map +1 -1
  144. package/dist/modules/auth/index.d.ts +1 -1
  145. package/dist/modules/auth/index.d.ts.map +1 -1
  146. package/dist/modules/auth/index.js +1 -1
  147. package/dist/modules/auth/index.js.map +1 -1
  148. package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
  149. package/dist/modules/auth/oidc-auth-provider.js +2 -9
  150. package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
  151. package/dist/modules/code-mode/code-mode.tool.js +2 -2
  152. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  153. package/dist/modules/connection-probe/connection-probe.service.d.ts +0 -12
  154. package/dist/modules/connection-probe/connection-probe.service.d.ts.map +1 -1
  155. package/dist/modules/connection-probe/connection-probe.service.js +1 -38
  156. package/dist/modules/connection-probe/connection-probe.service.js.map +1 -1
  157. package/dist/modules/database/core-schema.d.ts +68 -0
  158. package/dist/modules/database/core-schema.d.ts.map +1 -1
  159. package/dist/modules/database/core-schema.js +33 -0
  160. package/dist/modules/database/core-schema.js.map +1 -1
  161. package/dist/modules/declared-variables/declared-variables.routes.d.ts +8 -1
  162. package/dist/modules/declared-variables/declared-variables.routes.d.ts.map +1 -1
  163. package/dist/modules/declared-variables/declared-variables.routes.js +13 -2
  164. package/dist/modules/declared-variables/declared-variables.routes.js.map +1 -1
  165. package/dist/modules/embed/embed.tools.d.ts.map +1 -1
  166. package/dist/modules/embed/embed.tools.js +5 -0
  167. package/dist/modules/embed/embed.tools.js.map +1 -1
  168. package/dist/modules/kb-fs/index.d.ts +2 -0
  169. package/dist/modules/kb-fs/index.d.ts.map +1 -1
  170. package/dist/modules/kb-fs/index.js +1 -0
  171. package/dist/modules/kb-fs/index.js.map +1 -1
  172. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
  173. package/dist/modules/kb-fs/locking-filesystem.js +7 -1
  174. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
  175. package/dist/modules/kb-fs/repo-path.d.ts +26 -6
  176. package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
  177. package/dist/modules/kb-fs/repo-path.js +22 -7
  178. package/dist/modules/kb-fs/repo-path.js.map +1 -1
  179. package/dist/modules/kb-fs/tmp-ref.d.ts +36 -0
  180. package/dist/modules/kb-fs/tmp-ref.d.ts.map +1 -0
  181. package/dist/modules/kb-fs/tmp-ref.js +76 -0
  182. package/dist/modules/kb-fs/tmp-ref.js.map +1 -0
  183. package/dist/modules/mcp/mcp-auth.middleware.d.ts +7 -0
  184. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  185. package/dist/modules/mcp/mcp-auth.middleware.js +14 -1
  186. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  187. package/dist/modules/mcp/mcp.routes.d.ts +7 -1
  188. package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
  189. package/dist/modules/mcp/mcp.routes.js +39 -4
  190. package/dist/modules/mcp/mcp.routes.js.map +1 -1
  191. package/dist/modules/mcp/mcp.service.d.ts +11 -0
  192. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  193. package/dist/modules/mcp/mcp.service.js +74 -48
  194. package/dist/modules/mcp/mcp.service.js.map +1 -1
  195. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -1
  196. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  197. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +45 -10
  198. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  199. package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts +9 -0
  200. package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
  201. package/dist/modules/mcp/oauth/oauth-consent.routes.js +34 -1
  202. package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
  203. package/dist/modules/plugins/index.d.ts +1 -1
  204. package/dist/modules/plugins/index.d.ts.map +1 -1
  205. package/dist/modules/plugins/index.js.map +1 -1
  206. package/dist/modules/plugins/plugin-links.d.ts +18 -2
  207. package/dist/modules/plugins/plugin-links.d.ts.map +1 -1
  208. package/dist/modules/plugins/plugin-links.js.map +1 -1
  209. package/dist/modules/plugins/plugins.routes.d.ts +5 -1
  210. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  211. package/dist/modules/plugins/plugins.routes.js +32 -5
  212. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  213. package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +11 -1
  214. package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
  215. package/dist/modules/secrets-vault/db-secrets-vault.service.js +159 -24
  216. package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
  217. package/dist/modules/secrets-vault/index.d.ts +1 -1
  218. package/dist/modules/secrets-vault/index.d.ts.map +1 -1
  219. package/dist/modules/secrets-vault/index.js +1 -1
  220. package/dist/modules/secrets-vault/index.js.map +1 -1
  221. package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +53 -11
  222. package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
  223. package/dist/modules/secrets-vault/secrets-vault.contract.js +27 -0
  224. package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
  225. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +9 -0
  226. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
  227. package/dist/modules/secrets-vault/secrets-vault.routes.js +100 -42
  228. package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
  229. package/dist/modules/settings/oidc-check.d.ts.map +1 -1
  230. package/dist/modules/settings/oidc-check.js +5 -17
  231. package/dist/modules/settings/oidc-check.js.map +1 -1
  232. package/dist/modules/skills/index.d.ts +3 -3
  233. package/dist/modules/skills/index.d.ts.map +1 -1
  234. package/dist/modules/skills/index.js +0 -2
  235. package/dist/modules/skills/index.js.map +1 -1
  236. package/dist/modules/skills/skills.contract.d.ts +16 -0
  237. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  238. package/dist/modules/skills/skills.service.d.ts.map +1 -1
  239. package/dist/modules/skills/skills.service.js +6 -1
  240. package/dist/modules/skills/skills.service.js.map +1 -1
  241. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  242. package/dist/modules/skills/skills.tools.js +25 -13
  243. package/dist/modules/skills/skills.tools.js.map +1 -1
  244. package/dist/modules/tool-auth/external-api-key.interface.d.ts +11 -0
  245. package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
  246. package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
  247. package/dist/modules/tool-auth/external-api-key.service.d.ts +1 -0
  248. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  249. package/dist/modules/tool-auth/external-api-key.service.js +4 -1
  250. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  251. package/dist/modules/tool-auth/index.d.ts +1 -1
  252. package/dist/modules/tool-auth/index.d.ts.map +1 -1
  253. package/dist/modules/tool-auth/internal-token.service.d.ts +9 -0
  254. package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
  255. package/dist/modules/tool-auth/internal-token.service.js +1 -0
  256. package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
  257. package/dist/modules/tool-auth/tool-auth.middleware.d.ts +27 -3
  258. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  259. package/dist/modules/tool-auth/tool-auth.middleware.js +32 -6
  260. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  261. package/dist/modules/tool-helpers/tool-context.d.ts +7 -0
  262. package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
  263. package/dist/modules/tool-helpers/tool-context.js +3 -0
  264. package/dist/modules/tool-helpers/tool-context.js.map +1 -1
  265. package/dist/modules/tool-helpers/tool-def.js +1 -1
  266. package/dist/modules/tool-helpers/tool-def.js.map +1 -1
  267. package/dist/modules/tool-helpers/tool-handler.d.ts +15 -0
  268. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  269. package/dist/modules/tool-helpers/tool-handler.js +19 -0
  270. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  271. package/dist/modules/tool-helpers/tool.contract.d.ts +10 -0
  272. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  273. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  274. package/dist/modules/tool-helpers/validate-token.d.ts +3 -1
  275. package/dist/modules/tool-helpers/validate-token.d.ts.map +1 -1
  276. package/dist/modules/tool-helpers/validate-token.js +1 -1
  277. package/dist/modules/tool-helpers/validate-token.js.map +1 -1
  278. package/dist/modules/tool-manuals/index.d.ts +3 -3
  279. package/dist/modules/tool-manuals/index.d.ts.map +1 -1
  280. package/dist/modules/tool-manuals/index.js +0 -2
  281. package/dist/modules/tool-manuals/index.js.map +1 -1
  282. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts +7 -1
  283. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
  284. package/dist/modules/tool-manuals/mcp-json-discovery.js +69 -38
  285. package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
  286. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +46 -6
  287. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  288. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +7 -1
  289. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
  290. package/dist/modules/tool-manuals/tool-manuals.routes.js +41 -4
  291. package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
  292. package/dist/modules/tool-manuals/tool-manuals.service.d.ts +24 -13
  293. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  294. package/dist/modules/tool-manuals/tool-manuals.service.js +138 -59
  295. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  296. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +8 -2
  297. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  298. package/dist/modules/tool-manuals/tool-manuals.tools.js +56 -20
  299. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  300. package/dist/modules/tool-registry/manual.routes.d.ts +8 -1
  301. package/dist/modules/tool-registry/manual.routes.d.ts.map +1 -1
  302. package/dist/modules/tool-registry/manual.routes.js +27 -7
  303. package/dist/modules/tool-registry/manual.routes.js.map +1 -1
  304. package/dist/modules/tool-registry/tool.contract.d.ts +7 -0
  305. package/dist/modules/tool-registry/tool.contract.d.ts.map +1 -1
  306. package/dist/modules/tool-registry/tool.contract.js.map +1 -1
  307. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  308. package/dist/modules/workflow/agent-tools/workflow.tools.js +36 -27
  309. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  310. package/dist/modules/workflow/git/git.service.d.ts +2 -2
  311. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  312. package/dist/modules/workflow/git/git.service.js +20 -35
  313. package/dist/modules/workflow/git/git.service.js.map +1 -1
  314. package/dist/modules/workflow/workflow.service.d.ts +2 -1
  315. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  316. package/dist/modules/workflow/workflow.service.js +3 -0
  317. package/dist/modules/workflow/workflow.service.js.map +1 -1
  318. package/dist/modules/workspace/agent-access.gate.d.ts +1 -1
  319. package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -1
  320. package/dist/modules/workspace/agent-access.gate.js +1 -1
  321. package/dist/modules/workspace/agent-access.gate.js.map +1 -1
  322. package/dist/modules/workspace/file-readers/doc-extract.service.d.ts +28 -4
  323. package/dist/modules/workspace/file-readers/doc-extract.service.d.ts.map +1 -1
  324. package/dist/modules/workspace/file-readers/doc-extract.service.js +61 -40
  325. package/dist/modules/workspace/file-readers/doc-extract.service.js.map +1 -1
  326. package/dist/modules/workspace/index.d.ts +8 -0
  327. package/dist/modules/workspace/index.d.ts.map +1 -1
  328. package/dist/modules/workspace/index.js +8 -0
  329. package/dist/modules/workspace/index.js.map +1 -1
  330. package/dist/modules/workspace/nul-scan.d.ts +19 -0
  331. package/dist/modules/workspace/nul-scan.d.ts.map +1 -0
  332. package/dist/modules/workspace/nul-scan.js +55 -0
  333. package/dist/modules/workspace/nul-scan.js.map +1 -0
  334. package/dist/modules/workspace/pattern-matcher.contract.d.ts +110 -27
  335. package/dist/modules/workspace/pattern-matcher.contract.d.ts.map +1 -1
  336. package/dist/modules/workspace/pattern-matcher.contract.js +30 -13
  337. package/dist/modules/workspace/pattern-matcher.contract.js.map +1 -1
  338. package/dist/modules/workspace/ripgrep-search.d.ts +54 -0
  339. package/dist/modules/workspace/ripgrep-search.d.ts.map +1 -0
  340. package/dist/modules/workspace/ripgrep-search.js +657 -0
  341. package/dist/modules/workspace/ripgrep-search.js.map +1 -0
  342. package/dist/modules/workspace/ripgrep-version.d.ts +15 -0
  343. package/dist/modules/workspace/ripgrep-version.d.ts.map +1 -0
  344. package/dist/modules/workspace/ripgrep-version.js +31 -0
  345. package/dist/modules/workspace/ripgrep-version.js.map +1 -0
  346. package/dist/modules/workspace/startup/beside-checkout.d.ts +8 -5
  347. package/dist/modules/workspace/startup/beside-checkout.d.ts.map +1 -1
  348. package/dist/modules/workspace/startup/beside-checkout.js +14 -5
  349. package/dist/modules/workspace/startup/beside-checkout.js.map +1 -1
  350. package/dist/modules/workspace/worker-pool.d.ts +3 -4
  351. package/dist/modules/workspace/worker-pool.d.ts.map +1 -1
  352. package/dist/modules/workspace/worker-pool.js.map +1 -1
  353. package/dist/modules/workspace/workspace-tmp.d.ts +146 -0
  354. package/dist/modules/workspace/workspace-tmp.d.ts.map +1 -0
  355. package/dist/modules/workspace/workspace-tmp.js +435 -0
  356. package/dist/modules/workspace/workspace-tmp.js.map +1 -0
  357. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  358. package/dist/modules/workspace/workspace.routes.js +5 -26
  359. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  360. package/dist/modules/workspace/workspace.service.d.ts +47 -6
  361. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  362. package/dist/modules/workspace/workspace.service.js +86 -6
  363. package/dist/modules/workspace/workspace.service.js.map +1 -1
  364. package/dist/modules/workspace/workspace.tools.d.ts +3 -2
  365. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  366. package/dist/modules/workspace/workspace.tools.js +531 -137
  367. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  368. package/dist/shared/oauth-token-error.d.ts +23 -0
  369. package/dist/shared/oauth-token-error.d.ts.map +1 -0
  370. package/dist/shared/oauth-token-error.js +47 -0
  371. package/dist/shared/oauth-token-error.js.map +1 -0
  372. package/dist/shared/path-order.d.ts +28 -8
  373. package/dist/shared/path-order.d.ts.map +1 -1
  374. package/dist/shared/path-order.js +37 -8
  375. package/dist/shared/path-order.js.map +1 -1
  376. package/dist/shared/read-capped.d.ts +19 -0
  377. package/dist/shared/read-capped.d.ts.map +1 -0
  378. package/dist/shared/read-capped.js +45 -0
  379. package/dist/shared/read-capped.js.map +1 -0
  380. package/migrations/0022_session_version.sql +6 -0
  381. package/migrations/0023_agent_path.sql +3 -0
  382. package/migrations/meta/0022_snapshot.json +2360 -0
  383. package/migrations/meta/0023_snapshot.json +2378 -0
  384. package/migrations/meta/_journal.json +14 -0
  385. package/package.json +4 -3
  386. package/src/__tests__/hexis-catalog.ts +144 -0
  387. package/src/__tests__/pg-scratch.test.ts +69 -0
  388. package/src/__tests__/pg-scratch.ts +31 -0
  389. package/src/__tests__/real-disk.ts +21 -0
  390. package/src/__tests__/slow-ripgrep.ts +65 -0
  391. package/src/__tests__/tokens.ts +19 -0
  392. package/src/__tests__/tool-manuals.ts +32 -0
  393. package/src/__tests__/tool-wiring.ts +25 -0
  394. package/src/core/__tests__/boot-without-ripgrep.test.ts +34 -0
  395. package/src/core/__tests__/catalog-revision.test.ts +89 -0
  396. package/src/core/__tests__/damaged-document.tools.test.ts +2 -2
  397. package/src/core/__tests__/gated-boot.test.ts +8 -1
  398. package/src/core/__tests__/lifecycle.test.ts +46 -13
  399. package/src/core/__tests__/oauth-sign-in-real-file.test.ts +179 -0
  400. package/src/core/catalog-revision.ts +24 -2
  401. package/src/core/core-ports.ts +42 -0
  402. package/src/core/create-core-server.ts +14 -1
  403. package/src/core/create-core-services.ts +62 -15
  404. package/src/core/lifecycle.ts +26 -2
  405. package/src/index.ts +1 -1
  406. package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +12 -0
  407. package/src/modules/access/__tests__/access-control.colliding-name-cases.test.ts +113 -0
  408. package/src/modules/access/__tests__/access-control.frontmatter-head.test.ts +117 -0
  409. package/src/modules/access/__tests__/access-control.group-named-admin.test.ts +122 -0
  410. package/src/modules/access/__tests__/access-control.preview-relocation.test.ts +50 -0
  411. package/src/modules/access/__tests__/access-declarations.test.ts +19 -0
  412. package/src/modules/access/__tests__/access-groups.test.ts +55 -22
  413. package/src/modules/access/__tests__/access-mutation.service.test.ts +85 -59
  414. package/src/modules/access/__tests__/access-restrict-here.test.ts +7 -5
  415. package/src/modules/access/__tests__/access.routes.admins.test.ts +1 -0
  416. package/src/modules/access/__tests__/access.routes.batch.test.ts +2 -0
  417. package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +2 -0
  418. package/src/modules/access/__tests__/access.routes.group-grant.test.ts +50 -14
  419. package/src/modules/access/__tests__/access.routes.overrides.test.ts +2 -1
  420. package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +1 -0
  421. package/src/modules/access/__tests__/access.routes.prospective.test.ts +52 -10
  422. package/src/modules/access/__tests__/access.routes.revoke.test.ts +18 -13
  423. package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +2 -0
  424. package/src/modules/access/__tests__/grant-sources.test.ts +20 -15
  425. package/src/modules/access/__tests__/groups-admin.routes.test.ts +61 -0
  426. package/src/modules/access/__tests__/groups-admin.service.test.ts +211 -18
  427. package/src/modules/access/__tests__/holder-changes.test.ts +127 -0
  428. package/src/modules/access/__tests__/reference-scan.test.ts +366 -14
  429. package/src/modules/access/__tests__/roles-admin.security.test.ts +1 -1
  430. package/src/modules/access/__tests__/roles-admin.service.test.ts +43 -22
  431. package/src/modules/access/__tests__/roles-capabilities.test.ts +6 -2
  432. package/src/modules/access/__tests__/roles.routes.test.ts +1 -1
  433. package/src/modules/access/access-control.interface.ts +43 -10
  434. package/src/modules/access/access-control.service.ts +250 -72
  435. package/src/modules/access/access-mutation.service.ts +48 -53
  436. package/src/modules/access/access-view.ts +12 -10
  437. package/src/modules/access/access.routes.ts +66 -37
  438. package/src/modules/access/groups-admin.routes.ts +4 -2
  439. package/src/modules/access/groups-admin.service.ts +114 -44
  440. package/src/modules/access/groups-edit.ts +1 -1
  441. package/src/modules/access/index.ts +1 -1
  442. package/src/modules/access/reference-scan.ts +319 -46
  443. package/src/modules/access/roles-admin.service.ts +43 -26
  444. package/src/modules/access/synced-groups-writer.ts +1 -1
  445. package/src/modules/access-model/__tests__/access-grammar.test.ts +38 -1
  446. package/src/modules/access-model/__tests__/access-splice.test.ts +50 -8
  447. package/src/modules/access-model/access-grammar.ts +65 -20
  448. package/src/modules/access-model/access-splice.ts +31 -23
  449. package/src/modules/access-model/group-files.ts +5 -0
  450. package/src/modules/agent-guide/__tests__/agent-guide.test.ts +110 -25
  451. package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +38 -11
  452. package/src/modules/agent-guide/__tests__/guide-budget.test.ts +95 -0
  453. package/src/modules/agent-guide/__tests__/guide-eval.test.ts +182 -0
  454. package/src/modules/agent-guide/__tests__/guide-matches-code.test.ts +263 -0
  455. package/src/modules/agent-guide/__tests__/tool-surface.json +514 -0
  456. package/src/modules/agent-guide/__tests__/tool-surface.test.ts +75 -0
  457. package/src/modules/agent-guide/agent-guide.tools.ts +51 -22
  458. package/src/modules/agent-guide/agent-guide.ts +120 -13
  459. package/src/modules/agent-guide/guide-budget.ts +73 -0
  460. package/src/modules/agent-guide/index.ts +12 -0
  461. package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +5 -4
  462. package/src/modules/agent-instructions/compose.ts +7 -1
  463. package/src/modules/agent-instructions/shared-file-rules.ts +27 -9
  464. package/src/modules/agent-scope/__tests__/agent-surfaces.connect-as.test.ts +421 -0
  465. package/src/modules/agent-scope/__tests__/connection-scope.test.ts +46 -0
  466. package/src/modules/agent-scope/__tests__/in-memory-kb.ts +148 -0
  467. package/src/modules/agent-scope/agent-scope.contract.ts +83 -0
  468. package/src/modules/agent-scope/connection-scope.ts +199 -0
  469. package/src/modules/agent-scope/index.ts +15 -0
  470. package/src/modules/audit/agent-audit.service.ts +2 -0
  471. package/src/modules/audit/audit.contract.ts +6 -0
  472. package/src/modules/auth/__tests__/auth.middleware.test.ts +30 -2
  473. package/src/modules/auth/__tests__/auth.routes.test.ts +71 -5
  474. package/src/modules/auth/__tests__/auth.service.test.ts +90 -9
  475. package/src/modules/auth/__tests__/password-change-sessions.pg.test.ts +269 -0
  476. package/src/modules/auth/account-admission.ts +19 -0
  477. package/src/modules/auth/auth.middleware.ts +45 -4
  478. package/src/modules/auth/auth.routes.ts +46 -29
  479. package/src/modules/auth/auth.service.ts +135 -34
  480. package/src/modules/auth/index.ts +2 -0
  481. package/src/modules/auth/oidc-auth-provider.ts +2 -9
  482. package/src/modules/code-mode/code-mode.tool.ts +2 -2
  483. package/src/modules/connection-probe/__tests__/connection-probe.service.test.ts +5 -24
  484. package/src/modules/connection-probe/connection-probe.service.ts +1 -34
  485. package/src/modules/database/__tests__/session-version-migration.pg.test.ts +133 -0
  486. package/src/modules/database/core-schema.ts +33 -0
  487. package/src/modules/declared-variables/__tests__/declared-variables.route.test.ts +44 -1
  488. package/src/modules/declared-variables/declared-variables.routes.ts +12 -1
  489. package/src/modules/embed/__tests__/embed.tools.test.ts +12 -0
  490. package/src/modules/embed/embed.tools.ts +5 -0
  491. package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +25 -0
  492. package/src/modules/kb-fs/__tests__/repo-path.test.ts +53 -8
  493. package/src/modules/kb-fs/index.ts +2 -0
  494. package/src/modules/kb-fs/locking-filesystem.ts +7 -1
  495. package/src/modules/kb-fs/repo-path.ts +33 -7
  496. package/src/modules/kb-fs/tmp-ref.ts +81 -0
  497. package/src/modules/mcp/__tests__/connect-as.pg.test.ts +137 -0
  498. package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +29 -5
  499. package/src/modules/mcp/__tests__/mcp-routes-harness.ts +8 -0
  500. package/src/modules/mcp/__tests__/mcp.connect-as.test.ts +195 -0
  501. package/src/modules/mcp/__tests__/mcp.routes.connect-as.test.ts +192 -0
  502. package/src/modules/mcp/__tests__/oauth-consent.connect-as.test.ts +146 -0
  503. package/src/modules/mcp/mcp-auth.middleware.ts +19 -2
  504. package/src/modules/mcp/mcp.routes.ts +39 -3
  505. package/src/modules/mcp/mcp.service.ts +112 -52
  506. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +46 -8
  507. package/src/modules/mcp/oauth/oauth-consent.routes.ts +41 -1
  508. package/src/modules/plugins/__tests__/plugins.tools.test.ts +38 -2
  509. package/src/modules/plugins/index.ts +1 -1
  510. package/src/modules/plugins/plugin-links.ts +19 -2
  511. package/src/modules/plugins/plugins.routes.ts +35 -4
  512. package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +113 -0
  513. package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +430 -3
  514. package/src/modules/secrets-vault/__tests__/oauth-live-declaration.route.test.ts +218 -0
  515. package/src/modules/secrets-vault/__tests__/oauth-return-to.route.test.ts +39 -6
  516. package/src/modules/secrets-vault/db-secrets-vault.service.ts +170 -27
  517. package/src/modules/secrets-vault/index.ts +1 -1
  518. package/src/modules/secrets-vault/secrets-vault.contract.ts +61 -11
  519. package/src/modules/secrets-vault/secrets-vault.routes.ts +114 -42
  520. package/src/modules/settings/oidc-check.ts +5 -18
  521. package/src/modules/skills/__tests__/skills.service.test.ts +34 -0
  522. package/src/modules/skills/index.ts +7 -2
  523. package/src/modules/skills/skills.contract.ts +17 -0
  524. package/src/modules/skills/skills.service.ts +5 -1
  525. package/src/modules/skills/skills.tools.ts +45 -14
  526. package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +4 -0
  527. package/src/modules/tool-auth/external-api-key.interface.ts +11 -1
  528. package/src/modules/tool-auth/external-api-key.service.ts +5 -2
  529. package/src/modules/tool-auth/index.ts +1 -1
  530. package/src/modules/tool-auth/internal-token.service.ts +10 -0
  531. package/src/modules/tool-auth/tool-auth.middleware.ts +45 -3
  532. package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +19 -11
  533. package/src/modules/tool-helpers/__tests__/can-write.tools.test.ts +3 -1
  534. package/src/modules/tool-helpers/__tests__/route-tool-schemas.catalog.test.ts +38 -0
  535. package/src/modules/tool-helpers/tool-context.ts +10 -0
  536. package/src/modules/tool-helpers/tool-def.ts +1 -1
  537. package/src/modules/tool-helpers/tool-handler.ts +34 -0
  538. package/src/modules/tool-helpers/tool.contract.ts +10 -0
  539. package/src/modules/tool-helpers/validate-token.ts +4 -2
  540. package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +80 -4
  541. package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +197 -11
  542. package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +42 -0
  543. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +99 -8
  544. package/src/modules/tool-manuals/index.ts +6 -2
  545. package/src/modules/tool-manuals/mcp-json-discovery.ts +73 -34
  546. package/src/modules/tool-manuals/tool-manuals.contract.ts +40 -6
  547. package/src/modules/tool-manuals/tool-manuals.routes.ts +40 -3
  548. package/src/modules/tool-manuals/tool-manuals.service.ts +169 -61
  549. package/src/modules/tool-manuals/tool-manuals.tools.ts +81 -25
  550. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +6 -113
  551. package/src/modules/tool-registry/manual.routes.ts +26 -5
  552. package/src/modules/tool-registry/tool.contract.ts +7 -0
  553. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +1 -1
  554. package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +1 -1
  555. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +54 -3
  556. package/src/modules/workflow/agent-tools/workflow.tools.ts +36 -27
  557. package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +130 -3
  558. package/src/modules/workflow/git/git.service.ts +29 -48
  559. package/src/modules/workflow/workflow.service.ts +5 -0
  560. package/src/modules/workspace/__tests__/git-internals.security.test.ts +2 -2
  561. package/src/modules/workspace/__tests__/holds-nul.test.ts +66 -0
  562. package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +3 -3
  563. package/src/modules/workspace/__tests__/ripgrep-search.test.ts +570 -0
  564. package/src/modules/workspace/__tests__/ripgrep-version.test.ts +29 -0
  565. package/src/modules/workspace/__tests__/workspace-tmp.tools.test.ts +928 -0
  566. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +17 -37
  567. package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +20 -8
  568. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +10 -12
  569. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +22 -10
  570. package/src/modules/workspace/__tests__/workspace.tools.test.ts +549 -162
  571. package/src/modules/workspace/agent-access.gate.ts +1 -1
  572. package/src/modules/workspace/file-readers/__tests__/doc-extract.service.test.ts +10 -0
  573. package/src/modules/workspace/file-readers/doc-extract.service.ts +71 -38
  574. package/src/modules/workspace/index.ts +20 -0
  575. package/src/modules/workspace/nul-scan.ts +50 -0
  576. package/src/modules/workspace/pattern-matcher.contract.ts +123 -32
  577. package/src/modules/workspace/ripgrep-search.ts +715 -0
  578. package/src/modules/workspace/ripgrep-version.ts +35 -0
  579. package/src/modules/workspace/startup/__tests__/beside-checkout.test.ts +11 -0
  580. package/src/modules/workspace/startup/beside-checkout.ts +13 -5
  581. package/src/modules/workspace/worker-pool.ts +3 -4
  582. package/src/modules/workspace/workspace-tmp.ts +457 -0
  583. package/src/modules/workspace/workspace.routes.ts +6 -27
  584. package/src/modules/workspace/workspace.service.ts +92 -15
  585. package/src/modules/workspace/workspace.tools.ts +574 -176
  586. package/src/shared/__tests__/read-capped.test.ts +68 -0
  587. package/src/shared/oauth-token-error.ts +46 -0
  588. package/src/shared/path-order.ts +37 -8
  589. package/src/shared/read-capped.ts +40 -0
  590. package/starter-packs/engineering/Plugins/engineering-starter/access.md +1 -1
  591. package/starter-packs/general/Plugins/general-starter/access.md +1 -1
  592. package/starter-packs/operations/Plugins/operations-starter/access.md +1 -1
  593. package/starter-packs/sales/Plugins/sales-starter/access.md +1 -1
  594. package/starter-packs/support/Plugins/support-starter/access.md +1 -1
  595. package/agent-guide/finding-things.md +0 -7
  596. package/agent-guide/introduction.md +0 -27
  597. package/dist/modules/workspace/bounded-matcher.d.ts +0 -73
  598. package/dist/modules/workspace/bounded-matcher.d.ts.map +0 -1
  599. package/dist/modules/workspace/bounded-matcher.js +0 -200
  600. package/dist/modules/workspace/bounded-matcher.js.map +0 -1
  601. package/src/modules/workspace/__tests__/bounded-matcher.test.ts +0 -213
  602. package/src/modules/workspace/bounded-matcher.ts +0 -271
@@ -2,11 +2,11 @@ import type { Server as HttpServer } from 'node:http';
2
2
  import { createHmac } from 'node:crypto';
3
3
  import express from 'express';
4
4
  import { afterEach, describe, expect, it, vi } from 'vitest';
5
- import {
6
- createSecretsVaultRoutes,
7
- createSecretsVaultPublicRoutes,
8
- isSafeReturnPath,
9
- } from '../secrets-vault.routes.js';
5
+ import { createSecretsVaultRoutes, createSecretsVaultPublicRoutes, SecretOAuthError } from '../index.js';
6
+ // The one deliberate white-box reach: the open-redirect predicate is pinned as
7
+ // a unit table as well as end-to-end (see below), and it is on no entry because
8
+ // nothing outside the routes should decide what a safe return path is.
9
+ import { isSafeReturnPath } from '../secrets-vault.routes.js';
10
10
 
11
11
  /**
12
12
  * OAUTH `returnTo`: a sign-in started from a page other than /connect must land
@@ -54,6 +54,11 @@ const MANUALS = [
54
54
 
55
55
  const toolManualService = {
56
56
  listAccessible: async () => MANUALS,
57
+ // The sign-in start reads the declaration from the file; here it reads as the catalog does.
58
+ liveDeclaration: async (_who: string, slug: string, varName: string) => {
59
+ const manual = MANUALS.find((m) => m.slug === slug);
60
+ return manual ? { manual, variable: manual.variables.find((v) => v.name === varName) } : null;
61
+ },
57
62
  } as unknown as Parameters<typeof createSecretsVaultRoutes>[0]['toolManualService'];
58
63
 
59
64
  const accessControl = {
@@ -254,7 +259,35 @@ describe('GET /api/secrets/oauth/callback — where the browser lands', () => {
254
259
  const state = signState({ u: 'u1', i: 'secret-1', n: 'nonce', r: RETURN_TO });
255
260
  const location = await callback(base, state);
256
261
  expect(location.startsWith(`${FRONTEND}${RETURN_TO}#error=`)).toBe(true);
257
- // The user-facing message is generic — provider detail stays in the log.
262
+ // A failure that is not the provider's word stays generic: only the
263
+ // vault's own error carries a reason that was scrubbed for the page.
264
+ expect(decodeURIComponent(location.split('#error=')[1])).toBe(
265
+ 'Authorization failed. Check the provider configuration and try again.',
266
+ );
267
+ });
268
+
269
+ it("carries the provider's error code and Hexis's sentence to the page when the exchange was refused", async () => {
270
+ vi.spyOn(console, 'error').mockImplementation(() => {});
271
+ const refusal =
272
+ 'Token endpoint returned HTTP 400 (invalid_client). The client id or client secret this sign-in uses is not accepted by the provider. The tool owner should check the declared client id and set the client secret again.';
273
+ completeOAuth.mockImplementation(async () => {
274
+ throw new SecretOAuthError(refusal, 400);
275
+ });
276
+ const base = await baseUrl();
277
+ const state = signState({ u: 'u1', i: 'secret-1', n: 'nonce', r: RETURN_TO });
278
+ const location = await callback(base, state);
279
+ expect(decodeURIComponent(location.split('#error=')[1])).toBe(`Authorization failed. ${refusal}`);
280
+ });
281
+
282
+ it("keeps the generic message for the vault's own TRANSPORT errors, which quote what fetch said", async () => {
283
+ vi.spyOn(console, 'error').mockImplementation(() => {});
284
+ completeOAuth.mockImplementation(async () => {
285
+ // No provider status: not a provider's word, and never scrubbed.
286
+ throw new SecretOAuthError('Token endpoint request failed: fetch failed https://user:pw@provider.example/token');
287
+ });
288
+ const base = await baseUrl();
289
+ const state = signState({ u: 'u1', i: 'secret-1', n: 'nonce', r: RETURN_TO });
290
+ const location = await callback(base, state);
258
291
  expect(decodeURIComponent(location.split('#error=')[1])).toBe(
259
292
  'Authorization failed. Check the provider configuration and try again.',
260
293
  );
@@ -1,4 +1,5 @@
1
1
  import { createHash, randomBytes } from 'node:crypto';
2
+ import { isDeepStrictEqual } from 'node:util';
2
3
  import { logger } from '../../shared/logging.js';
3
4
 
4
5
  const log = logger('vault');
@@ -7,6 +8,8 @@ import type { Database } from '../database/index.js';
7
8
  import { secrets } from '../database/index.js';
8
9
  import { TokenCrypto } from '../../shared/token-crypto.js';
9
10
  import { assertSafeFetchUrl } from '../../shared/ssrf.js';
11
+ import { readCapped } from '../../shared/read-capped.js';
12
+ import { standardTokenErrorCode } from '../../shared/oauth-token-error.js';
10
13
  import {
11
14
  type ISecretsVaultService,
12
15
  type SecretSummary,
@@ -16,6 +19,7 @@ import {
16
19
  type PutSharedStaticSecretInput,
17
20
  type CreateOAuthSecretInput,
18
21
  type OAuthProviderConfig,
22
+ type DeclaredOAuthProvider,
19
23
  type VariableScopeResolver,
20
24
  type NamespaceSecretCount,
21
25
  isKeyInNamespace,
@@ -32,6 +36,79 @@ const MAX_VALUE_LEN = 100_000;
32
36
  const REFRESH_SKEW_MS = 60_000;
33
37
  /** Upper bound on a token-endpoint round-trip, so a hung provider can't block callers. */
34
38
  const TOKEN_REQUEST_TIMEOUT_MS = 10_000;
39
+ /** How much of a refusing token endpoint's body is read for its error code: an OAuth error object, never a whole error page. */
40
+ const TOKEN_ERROR_BODY_MAX_BYTES = 2048;
41
+ // Hexis's own sentences for a refused token request: what to change, in our
42
+ // words, never the provider's.
43
+ const HINT_CLIENT =
44
+ 'The client id or client secret this sign-in uses is not accepted by the provider. The tool owner should check the declared client id and set the client secret again.';
45
+ const HINT_GRANT =
46
+ 'The provider refused the grant: for a sign-in, a redirect URL mismatch or an expired or reused authorization code; for a refresh, a refresh token that has expired or been revoked. Sign in again.';
47
+ const HINT_SCOPE =
48
+ "The provider refused the scopes this sign-in asks for. The tool owner should check the declared scopes against the provider's app.";
49
+ const HINT_UNAUTHORIZED =
50
+ "The provider does not allow this client to use this grant. The tool owner should check the provider's app settings.";
51
+ const HINT_REQUEST =
52
+ 'The provider found the token request malformed. The tool owner should check the declared endpoints and parameters.';
53
+ const HINT_PROVIDER = 'The provider could not serve the token request. Try again later.';
54
+
55
+ /**
56
+ * Hexis's sentence for each error code a refusing token endpoint may answer:
57
+ * the standard ones (`shared/oauth-token-error.ts` is the one list of those,
58
+ * read here and by the OIDC configuration check) in `error`, and the ones
59
+ * HubSpot's token endpoint answers in its `status` field. Only a code from
60
+ * those lists is ever taken, exactly, so no other string a provider puts in a
61
+ * field — a secret, a secret with a suffix, free text — can reach a message.
62
+ * Nothing else of the body is ever taken: no description, no message, no first
63
+ * line. That is why a message built here needs no scrubbing, cutting or
64
+ * decoding, and why every spelling a secret could reach a log in (escaped,
65
+ * encoded, cut) stays inexpressible. A standard code without a sentence here
66
+ * (`access_denied`) is named and left unexplained.
67
+ */
68
+ const TOKEN_ERROR_HINTS: ReadonlyMap<string, string> = new Map([
69
+ ['invalid_request', HINT_REQUEST],
70
+ ['invalid_client', HINT_CLIENT],
71
+ ['invalid_grant', HINT_GRANT],
72
+ ['unauthorized_client', HINT_UNAUTHORIZED],
73
+ ['unsupported_grant_type', HINT_UNAUTHORIZED],
74
+ ['invalid_scope', HINT_SCOPE],
75
+ ['server_error', HINT_PROVIDER],
76
+ ['temporarily_unavailable', HINT_PROVIDER],
77
+ ['BAD_CLIENT_ID', HINT_CLIENT],
78
+ ['BAD_CLIENT_SECRET', HINT_CLIENT],
79
+ ['BAD_AUTH_CODE', HINT_GRANT],
80
+ ['BAD_REDIRECT_URI', HINT_GRANT],
81
+ ]);
82
+
83
+ /** The codes HubSpot's token endpoint answers in `status` instead of a standard `error`. */
84
+ const HUBSPOT_STATUS_CODES: ReadonlySet<string> = new Set(['BAD_CLIENT_ID', 'BAD_CLIENT_SECRET', 'BAD_AUTH_CODE', 'BAD_REDIRECT_URI']);
85
+
86
+ /**
87
+ * The known error code a refusing token endpoint answered, or null: a standard
88
+ * `error` (RFC 6749 §5.2), else HubSpot's own `status`, when it is exactly one
89
+ * of the codes known. Without even the code, a bare "HTTP 400" cannot tell a
90
+ * client the provider does not accept from a refused grant.
91
+ */
92
+ export function tokenEndpointErrorCode(bodyText: string): string | null {
93
+ const standard = standardTokenErrorCode(bodyText);
94
+ if (standard !== null) return standard;
95
+ let json: unknown;
96
+ try {
97
+ json = JSON.parse(bodyText);
98
+ } catch {
99
+ return null;
100
+ }
101
+ if (!json || typeof json !== 'object' || Array.isArray(json)) return null;
102
+ const { status } = json as Record<string, unknown>;
103
+ return typeof status === 'string' && HUBSPOT_STATUS_CODES.has(status) ? status : null;
104
+ }
105
+
106
+ /** The refusal in one message: the status, the known code when there is one, and Hexis's sentence for it. */
107
+ export function describeTokenEndpointRefusal(status: number, code: string | null): string {
108
+ const base = `Token endpoint returned HTTP ${status}`;
109
+ const hint = code === null ? undefined : TOKEN_ERROR_HINTS.get(code);
110
+ return hint ? `${base} (${code}). ${hint}` : base;
111
+ }
35
112
 
36
113
  /**
37
114
  * How a row is stored, or `null` when there is no row — the column is a plain
@@ -487,8 +564,9 @@ export class DbSecretsVaultService implements ISecretsVaultService {
487
564
  throw new InvalidSecretError('clientSecret is required');
488
565
  }
489
566
  // Validate the provider config the same way createOAuth does (SSRF-safe https
490
- // URLs). Store the client secret AND the provider meta together on the shared
491
- // row so a later `.tool` edit can't redirect the secret to another endpoint.
567
+ // URLs) and store it beside the secret as a record of what was saved — what
568
+ // `statusFor` and the setup surfaces read. A declared sign-in does not run
569
+ // from it: `beginToolOAuthByKey` is handed the declaration as it reads now.
492
570
  const provider = this.requireProvider({ ...input.provider, clientSecret: input.clientSecret });
493
571
  const blob: OAuthBlob = { clientSecret: provider.clientSecret };
494
572
  const valueEncrypted = this.crypto().encrypt(JSON.stringify(blob));
@@ -510,11 +588,15 @@ export class DbSecretsVaultService implements ISecretsVaultService {
510
588
  redirectUri: string;
511
589
  state: string;
512
590
  scopes?: string[];
591
+ provider?: DeclaredOAuthProvider;
513
592
  }): Promise<{ id: string; url: string }> {
514
593
  const userId = this.requireUserId(input.userId);
515
594
  const key = this.requireKey(input.key);
516
- // The shared row holds the owner-set client secret + the provider meta it was
517
- // set for. Both are read from HERE, never from the live `.tool`.
595
+ // The shared row holds the owner-set client secret. For a client the
596
+ // platform registered itself it also holds the provider configuration, the
597
+ // only description of that client there is; for a declared sign-in the
598
+ // configuration it holds is a record of the save, and `input.provider` (the
599
+ // tool file as it reads now) is what the sign-in follows.
518
600
  const [shared] = await this.db
519
601
  .select()
520
602
  .from(secrets)
@@ -524,11 +606,43 @@ export class DbSecretsVaultService implements ISecretsVaultService {
524
606
  throw new SecretOAuthError("This tool's owner hasn't finished setting this up");
525
607
  }
526
608
  const sharedBlob = this.readBlob(shared.valueEncrypted);
527
- const meta = this.readMeta(shared.oauthMeta);
528
- // A missing secret is "not set up yet" ONLY for confidential clients — a
529
- // dynamically-registered PUBLIC client (PKCE-only) never has one.
530
- if (!sharedBlob.clientSecret && !meta.publicClient) {
531
- throw new SecretOAuthError("This tool's owner hasn't finished setting this up");
609
+ let meta: OAuthProviderConfig;
610
+ let oauthMeta: typeof shared.oauthMeta;
611
+ if (input.provider) {
612
+ // A declared client is confidential: without the owner's secret there is
613
+ // nothing to sign in with, whatever the stored row says of itself.
614
+ if (!sharedBlob.clientSecret) {
615
+ throw new SecretOAuthError("This tool's owner hasn't finished setting this up");
616
+ }
617
+ // The save's own validation (https, no private or local address), BEFORE
618
+ // the caller's row is written or a URL built: a declaration that fails it
619
+ // sends nothing anywhere.
620
+ meta = this.requireProvider({ ...input.provider, publicClient: false });
621
+ oauthMeta = this.publicProviderMeta(meta);
622
+ // A MOVED PROPERTY, decided on the ticket (OAuth-Sign-In-Follows-The-Live-
623
+ // Declaration, 2026-10-27): the secret used to be bound to the token
624
+ // endpoint pinned at its save, so a later file edit could not send it
625
+ // anywhere else; now it goes where the file says at each start, so a
626
+ // writer of the tool file can move it (to an https, public host — the
627
+ // validation above still holds). Nothing refuses that; this line is the
628
+ // trail an operator reads when a secret went somewhere new.
629
+ const recorded = (shared.oauthMeta ?? {}) as { tokenUrl?: unknown };
630
+ if (typeof recorded.tokenUrl === 'string' && recorded.tokenUrl !== meta.tokenUrl) {
631
+ // Origins only: a path or query is the file's to say, not the log's.
632
+ const origin = (u: string) => (URL.canParse(u) ? new URL(u).origin : '(unparseable)');
633
+ log.warn(`sign-in on ${key}: the declared token endpoint differs from the one recorded when the client secret was saved`, {
634
+ savedOrigin: origin(recorded.tokenUrl),
635
+ declaredOrigin: origin(meta.tokenUrl),
636
+ });
637
+ }
638
+ } else {
639
+ meta = this.readMeta(shared.oauthMeta);
640
+ oauthMeta = shared.oauthMeta;
641
+ // A missing secret is "not set up yet" ONLY for confidential clients — a
642
+ // dynamically-registered PUBLIC client (PKCE-only) never has one.
643
+ if (!sharedBlob.clientSecret && !meta.publicClient) {
644
+ throw new SecretOAuthError("This tool's owner hasn't finished setting this up");
645
+ }
532
646
  }
533
647
 
534
648
  // Carry forward any tokens the caller already holds so re-entering the flow
@@ -543,12 +657,12 @@ export class DbSecretsVaultService implements ISecretsVaultService {
543
657
  // encKey must degrade to "start fresh", not crash the recovery flow — and (b)
544
658
  // they were minted under the SAME provider config we're about to (re)write, or
545
659
  // a refresh_token for a stale client id/secret would silently fail once carried
546
- // into this row after an owner rotates the shared secret.
660
+ // into this row after an owner rotates the shared secret or edits the
661
+ // declaration. Compared as values, not as serialised text: the stored side
662
+ // has been through jsonb, which does not keep key order, and a declared
663
+ // sign-in's side is built here.
547
664
  let existingTokens: OAuthTokenSet | undefined;
548
- if (
549
- existingUserRow?.kind === 'oauth' &&
550
- JSON.stringify(existingUserRow.oauthMeta) === JSON.stringify(shared.oauthMeta)
551
- ) {
665
+ if (existingUserRow?.kind === 'oauth' && isDeepStrictEqual(existingUserRow.oauthMeta, oauthMeta)) {
552
666
  try {
553
667
  existingTokens = this.readBlob(existingUserRow.valueEncrypted).tokens;
554
668
  } catch {
@@ -567,9 +681,13 @@ export class DbSecretsVaultService implements ISecretsVaultService {
567
681
  const requestedScopes = input.scopes && input.scopes.length ? input.scopes : meta.scopes;
568
682
  const pendingScopes = requestedScopes && requestedScopes.length ? requestedScopes.join(' ') : undefined;
569
683
 
570
- // Provision (or reset) the caller's own oauth row for this key from the shared
571
- // provider meta + secret, preserving any existing tokens. Keyed `<manual>_<VAR>`
572
- // so `resolve` (scope 'user') returns the token once sign-in completes.
684
+ // Provision (or reset) the caller's own oauth row for this key from the provider
685
+ // configuration this start uses + the shared secret, preserving any existing
686
+ // tokens. The row records that configuration because `completeOAuth` and every
687
+ // later refresh of this grant read it from HERE: the exchange goes to the token
688
+ // endpoint, under the client id, that the consent was given for. Keyed
689
+ // `<manual>_<VAR>` so `resolve` (scope 'user') returns the token once sign-in
690
+ // completes.
573
691
  const blob: OAuthBlob = {
574
692
  clientSecret: sharedBlob.clientSecret,
575
693
  tokens: existingTokens,
@@ -579,16 +697,15 @@ export class DbSecretsVaultService implements ISecretsVaultService {
579
697
  const valueEncrypted = this.crypto().encrypt(JSON.stringify(blob));
580
698
  const [row] = await this.db
581
699
  .insert(secrets)
582
- .values({ userId, key, kind: 'oauth', label: shared.label, valueEncrypted, oauthMeta: shared.oauthMeta })
700
+ .values({ userId, key, kind: 'oauth', label: shared.label, valueEncrypted, oauthMeta })
583
701
  .onConflictDoUpdate({
584
702
  target: [secrets.userId, secrets.key],
585
- set: { kind: 'oauth', valueEncrypted, oauthMeta: shared.oauthMeta, updatedAt: new Date() },
703
+ set: { kind: 'oauth', valueEncrypted, oauthMeta, updatedAt: new Date() },
586
704
  })
587
705
  .returning();
588
706
 
589
- // Build the consent URL exactly as beginOAuth does, from the stored meta —
590
- // EXCEPT the requested scopes (above). Client id, addresses, and secret
591
- // stay owner-pinned.
707
+ // Build the consent URL exactly as beginOAuth does, from the configuration
708
+ // this start uses, with the requested scopes chosen above.
592
709
  const url = new URL(meta.authorizationUrl);
593
710
  url.searchParams.set('response_type', 'code');
594
711
  url.searchParams.set('client_id', meta.clientId);
@@ -732,7 +849,18 @@ export class DbSecretsVaultService implements ISecretsVaultService {
732
849
  err instanceof SecretOAuthError &&
733
850
  (err.providerStatus === 400 || err.providerStatus === 401)
734
851
  ) {
735
- return this.wipeTokens(row, blob);
852
+ // The only trace of a sign-in that silently turns into "not connected":
853
+ // say which one, the provider's reason, and what the guarded wipe did —
854
+ // a concurrent refresh may have rotated the tokens first, in which case
855
+ // nothing was wiped and the rotated grant is served.
856
+ const result = await this.wipeTokens(row, blob);
857
+ const did = result.wiped
858
+ ? 'sign-in wiped'
859
+ : result.outcome === 'refreshed'
860
+ ? 'the row had been rotated by a concurrent refresh meanwhile; the rotated sign-in is kept'
861
+ : 'the row had been removed or replaced meanwhile; nothing wiped';
862
+ log.warn(`refresh refused for "${row.key}", ${did}: ${err.message}`);
863
+ return result;
736
864
  }
737
865
  return { outcome: 'transient' };
738
866
  }
@@ -794,7 +922,10 @@ export class DbSecretsVaultService implements ISecretsVaultService {
794
922
  private async wipeTokens(
795
923
  row: typeof secrets.$inferSelect,
796
924
  blob: OAuthBlob,
797
- ): Promise<{ outcome: 'refreshed'; accessToken: string } | { outcome: 'rejected'; accessToken: null }> {
925
+ ): Promise<
926
+ | { outcome: 'refreshed'; accessToken: string; wiped: false }
927
+ | { outcome: 'rejected'; accessToken: null; wiped: boolean }
928
+ > {
798
929
  const next: OAuthBlob = { clientSecret: blob.clientSecret };
799
930
  // Guarded on the ciphertext we read, so a concurrent refresh that
800
931
  // already persisted ROTATED tokens can't be wiped by our stale
@@ -807,13 +938,17 @@ export class DbSecretsVaultService implements ISecretsVaultService {
807
938
  .where(and(eq(secrets.id, row.id), eq(secrets.valueEncrypted, row.valueEncrypted)))
808
939
  .returning({ id: secrets.id });
809
940
  if (Array.isArray(wiped) && wiped.length === 0) {
941
+ // `wiped: false` either way: this write matched nothing, and the caller
942
+ // reports what it did, not what it set out to do.
810
943
  const fresh = await this.currentAccessToken(row.id);
811
- return fresh ? { outcome: 'refreshed', accessToken: fresh } : { outcome: 'rejected', accessToken: null };
944
+ return fresh
945
+ ? { outcome: 'refreshed', accessToken: fresh, wiped: false }
946
+ : { outcome: 'rejected', accessToken: null, wiped: false };
812
947
  }
813
948
  // The sign-in just became not-connected — let credential-validity caches
814
949
  // (pooled downstream connections dialed with the dead token) drop it now.
815
950
  this.notifyMutation(row.userId);
816
- return { outcome: 'rejected', accessToken: null };
951
+ return { outcome: 'rejected', accessToken: null, wiped: true };
817
952
  }
818
953
 
819
954
  async putSharedOAuthProvider(input: {
@@ -887,7 +1022,15 @@ export class DbSecretsVaultService implements ISecretsVaultService {
887
1022
  throw new SecretOAuthError(msg);
888
1023
  }
889
1024
  if (!res.ok) {
890
- throw new SecretOAuthError(`Token endpoint returned HTTP ${res.status}`, res.status);
1025
+ // The provider's error CODE rides in the message: the callback route and
1026
+ // the refresh path log that message, and it is the one line that says
1027
+ // WHY a sign-in failed (a client the provider does not accept, a refused
1028
+ // authorization code). Best effort: the STATUS is the contract (the
1029
+ // refresh path reads 400/401 off it to retire a dead grant), and a body
1030
+ // that fails to read must not turn a definitive refusal into an
1031
+ // unclassified error.
1032
+ const code = tokenEndpointErrorCode(await readCapped(res, TOKEN_ERROR_BODY_MAX_BYTES).catch(() => ''));
1033
+ throw new SecretOAuthError(describeTokenEndpointRefusal(res.status, code), res.status);
891
1034
  }
892
1035
  const json = (await res.json()) as Record<string, unknown>;
893
1036
  const accessToken = typeof json.access_token === 'string' ? json.access_token : '';
@@ -27,4 +27,4 @@ export {
27
27
  DEFAULT_SECRETS_SCOPE,
28
28
  } from './secrets-variable-loader.js';
29
29
  // Reached by other modules; exported here so they stop importing the files.
30
- export { scopesCovered } from './secrets-vault.contract.js';
30
+ export { scopesCovered, userSecretGap, type UserSecretGap } from './secrets-vault.contract.js';
@@ -74,6 +74,13 @@ export interface SecretConfigStatus {
74
74
  grantedScopes?: string;
75
75
  }
76
76
 
77
+ /**
78
+ * A declared sign-in's public provider configuration, as the tool file reads it:
79
+ * everything but the secret (only the owner's shared row holds it) and the
80
+ * public-client flag (a declared client is confidential by definition).
81
+ */
82
+ export type DeclaredOAuthProvider = Omit<OAuthProviderConfig, 'clientSecret' | 'publicClient'>;
83
+
77
84
  /**
78
85
  * How a secret is provisioned — mirrors `ToolVariableScope` in the tool-manuals
79
86
  * contract, redeclared here to keep the vault free of a tool-manuals import.
@@ -186,8 +193,10 @@ export interface ISecretsVaultService {
186
193
  /**
187
194
  * Set the confidential client secret + provider config for a tool's OAuth-backed
188
195
  * variable, on the SHARED row (`user_id NULL`) for `key`. The provider meta is
189
- * stored ALONGSIDE the secret so a later `.tool` edit can't redirect it. Owner-
190
- * gated by the route (write access on the `.tool`).
196
+ * stored ALONGSIDE the secret as a record of the save; a sign-in follows the
197
+ * declaration as the tool file reads when it starts (`beginToolOAuthByKey`'s
198
+ * `provider`), not this copy. Owner-gated by the route (write access on the
199
+ * `.tool`).
191
200
  */
192
201
  putSharedOAuthClientSecret(input: {
193
202
  key: string;
@@ -212,16 +221,26 @@ export interface ISecretsVaultService {
212
221
  */
213
222
  getSharedOAuthProvider(key: string): Promise<OAuthProviderConfig | null>;
214
223
  /**
215
- * Provision (if needed) the caller's per-user oauth row for `key` from the SHARED
216
- * row's stored provider meta + client secret, then build the consent URL. 409 if
217
- * the owner hasn't set the client secret. Returns the provisioned row's id (to
218
- * sign into the callback state) and the consent url.
224
+ * Provision (if needed) the caller's per-user oauth row for `key` and build the
225
+ * consent URL. 409 if the owner hasn't set the client secret. Returns the
226
+ * provisioned row's id (to sign into the callback state) and the consent url.
219
227
  *
220
- * `scopes`, when given, are the permissions to REQUEST on the consent screen —
221
- * supplied by the caller from the live tool file so a scope added there takes
222
- * effect without the owner re-entering the secret. Only the requested scopes
223
- * follow the tool file; the client id, addresses, and secret are still read from
224
- * the owner-set shared row. Omitted → the shared row's stored scopes are used.
228
+ * Where the provider configuration comes from depends on who registered the
229
+ * client, and each kind of sign-in has exactly one source:
230
+ * - `provider` given — a DECLARED sign-in: the client id, endpoints, PKCE,
231
+ * resource and authorize params are the declaration as the tool file reads
232
+ * now, handed over by the caller. They are validated as the client-secret
233
+ * save validates them (https, no private or local address) before anything
234
+ * is written or built, and they are what the caller's row records, so the
235
+ * token exchange and later refreshes of this grant use the configuration
236
+ * the consent was given for. The shared row contributes the client secret
237
+ * only, and must hold one.
238
+ * - `provider` absent — a client the platform registered itself (dynamic
239
+ * client registration): nothing in a file describes it, so the shared
240
+ * row's stored configuration is used.
241
+ *
242
+ * `scopes`, when given, are the permissions to REQUEST on the consent screen
243
+ * (the live tool file's); omitted → the provider configuration's own scopes.
225
244
  */
226
245
  beginToolOAuthByKey(input: {
227
246
  userId: string;
@@ -229,6 +248,7 @@ export interface ISecretsVaultService {
229
248
  redirectUri: string;
230
249
  state: string;
231
250
  scopes?: string[];
251
+ provider?: DeclaredOAuthProvider;
232
252
  }): Promise<{ id: string; url: string }>;
233
253
 
234
254
  /**
@@ -342,6 +362,36 @@ export function scopesCovered(required: string[] | undefined, granted: string |
342
362
  return missingScopes(required, granted).length === 0;
343
363
  }
344
364
 
365
+ /** Why a caller's own value for a per-user variable is not ready yet; see {@link userSecretGap}. */
366
+ export type UserSecretGap = 'missing' | 'wrong-kind' | 'unauthorized' | 'under-scoped';
367
+
368
+ /**
369
+ * Whether the CALLER's own value for a per-user variable is ready, and if not,
370
+ * why: the one rule the MCP proxy hides a tool by and `list_tool_setup` reports
371
+ * from, so the two cannot disagree. `null` when it is ready.
372
+ *
373
+ * - `missing`: no row at all, so it needs a value or a sign-in.
374
+ * - `wrong-kind`: a row of the OTHER kind than the manual now declares. A
375
+ * `.tool` edited from an OAuth sign-in to a plain key (or back) leaves the
376
+ * old row behind, and serving a token where a key is declared fails
377
+ * opaquely at the provider. The credential has to be set up again.
378
+ * - `unauthorized`: an OAuth row with no token yet: registered, sign-in not
379
+ * completed. Fail closed: anything but a confirmed `true` counts.
380
+ * - `under-scoped`: a token granted fewer scopes than the tool now declares,
381
+ * so the call would fail at the provider; it needs signing in again.
382
+ */
383
+ export function userSecretGap(
384
+ declared: { oauth: boolean; oauthScopes?: string[] },
385
+ status: Pick<SecretConfigStatus, 'userConfigured' | 'userAuthorized' | 'grantedScopes'> & { userKind?: SecretKind | null } | undefined,
386
+ ): UserSecretGap | null {
387
+ if (!status?.userConfigured) return 'missing';
388
+ const declaredKind: SecretKind = declared.oauth ? 'oauth' : 'static';
389
+ if (status.userKind && status.userKind !== declaredKind) return 'wrong-kind';
390
+ if (declared.oauth && status.userAuthorized !== true) return 'unauthorized';
391
+ if (declared.oauth && !scopesCovered(declared.oauthScopes, status.grantedScopes)) return 'under-scoped';
392
+ return null;
393
+ }
394
+
345
395
  /**
346
396
  * Is the stored row the KIND the manual now declares — i.e. would the thing the
347
397
  * UI is about to offer actually work?