@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
@@ -8,7 +8,18 @@ import {
8
8
  KbReferenceScanner,
9
9
  findRoleRefsInText,
10
10
  rewriteRoleTokensInText,
11
+ UNSHOWN_REFERENCE_REFUSAL,
12
+ translateRewriteWriteRefusal,
13
+ type PathVisibility,
11
14
  } from '../reference-scan.js';
15
+ import type { ITreeWalker } from '../../../shared/fs.contract.js';
16
+ import { NodeFs } from '../../kb-fs/node-fs.js';
17
+ import { AccessDeniedError } from '../../access-model/access-errors.js';
18
+
19
+ /** A renaming person who may read every path. */
20
+ const ALL_VISIBLE: PathVisibility = async (paths) => new Map(paths.map((p) => [p, true]));
21
+ /** A renaming person who may read none of them. */
22
+ const NONE_VISIBLE: PathVisibility = async (paths) => new Map(paths.map((p) => [p, false]));
12
23
 
13
24
  /**
14
25
  * Unit tests for the shared reference scanner's parse rules (comments,
@@ -158,6 +169,31 @@ async function write(repo: string, rel: string, contents: string): Promise<void>
158
169
  await fs.writeFile(abs, contents);
159
170
  }
160
171
 
172
+ describe('translateRewriteWriteRefusal — the lock scope still sees what a failed restore left', () => {
173
+ function refusal(unrestored?: Set<string>): AccessDeniedError {
174
+ const err = new AccessDeniedError({ path: 'Hidden/secret.md', eligibleRoles: [], eligibleUsers: [] });
175
+ if (unrestored) (err as { unrestoredPaths?: Set<string> }).unrestoredPaths = unrestored;
176
+ return err;
177
+ }
178
+
179
+ it('a refusal on a hidden file carries unrestoredPaths onto the generic refusal', async () => {
180
+ const unrestored = new Set(['KnowledgeBase/Hidden/secret.md']);
181
+ const out = await translateRewriteWriteRefusal(refusal(unrestored), NONE_VISIBLE, (m) => new Error(m));
182
+ expect((out as Error).message).toBe(UNSHOWN_REFERENCE_REFUSAL);
183
+ expect((out as { unrestoredPaths?: Set<string> }).unrestoredPaths).toBe(unrestored);
184
+ });
185
+
186
+ it('a refusal with every file restored adds no unrestoredPaths', async () => {
187
+ const out = await translateRewriteWriteRefusal(refusal(), NONE_VISIBLE, (m) => new Error(m));
188
+ expect((out as { unrestoredPaths?: Set<string> }).unrestoredPaths).toBeUndefined();
189
+ });
190
+
191
+ it('a refusal on a readable file comes back as it was', async () => {
192
+ const err = refusal(new Set(['x']));
193
+ expect(await translateRewriteWriteRefusal(err, ALL_VISIBLE, (m) => new Error(m))).toBe(err);
194
+ });
195
+ });
196
+
161
197
  describe('KbReferenceScanner — .tool frontmatter is scanned and rewritten like .md', () => {
162
198
  it('finds a .tool frontmatter grant and a rename rewrites it (body untouched)', async () => {
163
199
  const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-refscan-'));
@@ -166,13 +202,13 @@ describe('KbReferenceScanner — .tool frontmatter is scanned and rewritten like
166
202
  const toolBody = 'name: crm\ntype: mcp\n# not frontmatter below\n';
167
203
  await write(repo, 'Plugins/GTM/crm.tool', `---\nread:\n - GTM Team\n---\n${toolBody}`);
168
204
  await write(repo, 'Plugins/GTM/notes.md', '---\nowner: GTM Team\n---\n# notes\n');
169
- const scanner = new KbReferenceScanner(stubWorkspace(root), KB);
205
+ const scanner = new KbReferenceScanner(stubWorkspace(root), new NodeFs(), KB);
170
206
 
171
207
  const byToken = await scanner.scan('ws-1');
172
208
  const hits = (byToken.get('gtm team') ?? []).map((h) => h.path).sort();
173
209
  expect(hits).toEqual(['Plugins/GTM/crm.tool', 'Plugins/GTM/notes.md']);
174
210
 
175
- const writes = await scanner.rewriteReferences('ws-1', 'gtm team', 'Go To Market', (m) => new Error(m));
211
+ const writes = await scanner.rewriteReferences('ws-1', 'gtm team', 'Go To Market', (m) => new Error(m), ALL_VISIBLE);
176
212
  const toolWrite = writes.find((w) => w.repoRelativePath === 'Plugins/GTM/crm.tool')!;
177
213
  expect(toolWrite.content).toContain(' - Go To Market');
178
214
  // The .tool body below the frontmatter is byte-for-byte intact.
@@ -184,6 +220,321 @@ describe('KbReferenceScanner — .tool frontmatter is scanned and rewritten like
184
220
  });
185
221
  });
186
222
 
223
+ describe('KbReferenceScanner — candidates are the resolver\'s walk, not the explorer\'s', () => {
224
+ it('scans and rewrites an access.md that .bevelignore hides from the explorer', async () => {
225
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-refscan-ignore-'));
226
+ try {
227
+ const repo = path.join(root, KB);
228
+ await write(repo, '.bevelignore', 'Archive/\n');
229
+ // The resolver walks past .bevelignore, so this rule is ENFORCED.
230
+ await write(repo, 'Archive/access.md', '---\nwrite:\n - Admin\n---\n');
231
+ // The explorer's listing, as the real one answers: what .bevelignore
232
+ // hides is not in it. A scan built on it would miss Archive/access.md.
233
+ const base = stubWorkspace(root);
234
+ const ws = {
235
+ ...base,
236
+ listFiles: async (id: string) => {
237
+ const tree = await base.listFiles(id);
238
+ const prune = (n: FileTreeEntry): FileTreeEntry => ({
239
+ ...n,
240
+ children: n.children
241
+ ?.filter((c) => c.relativePath !== `${KB}/Archive`)
242
+ .map(prune),
243
+ });
244
+ return prune(tree);
245
+ },
246
+ } as unknown as IWorkspaceService;
247
+ const scanner = new KbReferenceScanner(ws, new NodeFs(), KB);
248
+
249
+ expect((await scanner.scan('ws-1')).get('admin')).toEqual([{ path: 'Archive/access.md', verb: 'write' }]);
250
+ const writes = await scanner.rewriteReferences('ws-1', 'admin', 'Admins', (m) => new Error(m), ALL_VISIBLE);
251
+ expect(writes.map((w) => w.repoRelativePath)).toEqual(['Archive/access.md']);
252
+ } finally {
253
+ await fs.rm(root, { recursive: true, force: true });
254
+ }
255
+ });
256
+ });
257
+
258
+ describe('KbReferenceScanner — references are what the resolver parses', () => {
259
+ // A body-governed access.md: the body is the folder's rules, the
260
+ // frontmatter the file's own — here a FLOW sequence, which only the
261
+ // resolver's full-YAML fallback reads. And a node whose owner is QUOTED.
262
+ const FLOW_ACCESS_MD = '---\nread: ["Admin"]\n---\nwrite:\n - Admin\n';
263
+ const QUOTED_NODE = "---\nowner: 'Admin'\n---\n# Doc\n";
264
+
265
+ it('the scan reports quoted and flow-YAML rules the resolver enforces', async () => {
266
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-refscan-yaml-'));
267
+ try {
268
+ const repo = path.join(root, KB);
269
+ await write(repo, 'Team/access.md', FLOW_ACCESS_MD);
270
+ await write(repo, 'Team/Doc.md', QUOTED_NODE);
271
+ const scanner = new KbReferenceScanner(stubWorkspace(root), new NodeFs(), KB);
272
+
273
+ const hits = (await scanner.scan('ws-1')).get('admin') ?? [];
274
+ expect(hits).toEqual(
275
+ expect.arrayContaining([
276
+ { path: 'Team/access.md', verb: 'write' },
277
+ { path: 'Team/access.md', verb: 'read' },
278
+ { path: 'Team/Doc.md', verb: 'owner' },
279
+ ]),
280
+ );
281
+ expect(hits).toHaveLength(3);
282
+ } finally {
283
+ await fs.rm(root, { recursive: true, force: true });
284
+ }
285
+ });
286
+
287
+ it('an access.md the resolver drops as broken references nothing', async () => {
288
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-refscan-broken-'));
289
+ try {
290
+ // A scalar where the verb needs a list: a hard parse error, so the
291
+ // resolver ignores the whole file.
292
+ await write(path.join(root, KB), 'Team/access.md', '---\nread: Admin\nwrite:\n - Admin\n---\n');
293
+ const scanner = new KbReferenceScanner(stubWorkspace(root), new NodeFs(), KB);
294
+ expect((await scanner.scan('ws-1')).get('admin')).toBeUndefined();
295
+ } finally {
296
+ await fs.rm(root, { recursive: true, force: true });
297
+ }
298
+ });
299
+
300
+ it('a rename rewrites quoted and flow-YAML references in place, quoting and comments kept', async () => {
301
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-refscan-structural-'));
302
+ try {
303
+ const repo = path.join(root, KB);
304
+ await write(repo, 'Team/access.md', FLOW_ACCESS_MD);
305
+ await write(repo, 'Team/Doc.md', "---\nnodeType: Doc # kept\nowner: 'Admin'\nread: [Sales, \"deny Admin\"]\n---\n# Doc\n");
306
+ const scanner = new KbReferenceScanner(stubWorkspace(root), new NodeFs(), KB);
307
+ const writes = await scanner.rewriteReferences('ws-1', 'admin', 'Admins', (m) => new Error(m), ALL_VISIBLE);
308
+ const byPath = new Map(writes.map((w) => [w.repoRelativePath, w.content]));
309
+ expect(byPath.get('Team/access.md')).toBe('---\nread: ["Admins"]\n---\nwrite:\n - Admins\n');
310
+ expect(byPath.get('Team/Doc.md')).toBe(
311
+ "---\nnodeType: Doc # kept\nowner: 'Admins'\nread: [Sales, \"deny Admins\"]\n---\n# Doc\n",
312
+ );
313
+ } finally {
314
+ await fs.rm(root, { recursive: true, force: true });
315
+ }
316
+ });
317
+
318
+ it('an alias under a verb key is rewritten without touching the anchor it points at', async () => {
319
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-refscan-alias-'));
320
+ try {
321
+ const repo = path.join(root, KB);
322
+ await write(repo, 'Team/Doc.md', '---\nteam: &t Admin\nread: [*t]\n---\n');
323
+ const scanner = new KbReferenceScanner(stubWorkspace(root), new NodeFs(), KB);
324
+ const [w] = await scanner.rewriteReferences('ws-1', 'admin', 'Admins', (m) => new Error(m), ALL_VISIBLE);
325
+ expect(w.content).toBe('---\nteam: &t Admin\nread: [Admins]\n---\n');
326
+ } finally {
327
+ await fs.rm(root, { recursive: true, force: true });
328
+ }
329
+ });
330
+
331
+ // Stated Contracts Are Checked, Not Trusted: the resolver reads `read: *team`
332
+ // as the anchored list's entries, so the rename rewrites them there — on a
333
+ // copy, leaving the anchor and its other uses as they were.
334
+ it.each([
335
+ [
336
+ 'the whole verb value',
337
+ '---\nteam: &team\n - Admin\n - Sales\nread: *team\n---\n# Doc\n',
338
+ '---\nteam: &team\n - Admin\n - Sales\nread:\n - Admins\n - Sales\n---\n# Doc\n',
339
+ ],
340
+ [
341
+ 'beside a quoted verb, an aliased item inside it',
342
+ "---\nlead: &lead Admin\nteam: &team\n - *lead\n - deny Admin\nread: *team\nowner: 'Sales'\n---\n",
343
+ "---\nlead: &lead Admin\nteam: &team\n - *lead\n - deny Admin\nread:\n - Admins\n - deny Admins\nowner: 'Sales'\n---\n",
344
+ ],
345
+ ])('an alias to an anchored list is rewritten as a copy (%s)', async (_label, before, after) => {
346
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-refscan-alias-list-'));
347
+ try {
348
+ const repo = path.join(root, KB);
349
+ await write(repo, 'Team/Doc.md', before);
350
+ const scanner = new KbReferenceScanner(stubWorkspace(root), new NodeFs(), KB);
351
+ const writes = await scanner.rewriteReferences('ws-1', 'admin', 'Admins', (m) => new Error(m), ALL_VISIBLE);
352
+ expect(writes.map((w) => [w.repoRelativePath, w.content])).toEqual([['Team/Doc.md', after]]);
353
+ } finally {
354
+ await fs.rm(root, { recursive: true, force: true });
355
+ }
356
+ });
357
+
358
+ // A Guard's Answer Is a Disclosure, and Widening a Primitive Traces Its
359
+ // Consequences: a file the renaming person cannot read (another person's
360
+ // private plugin) neither blocks the rename nor changes its answer —
361
+ // whatever it holds, the rename completes and the file is rewritten like
362
+ // any other, so no reference to the old name is stranded there.
363
+ it.each([
364
+ ['no reference', '---\nread:\n - Sales\n---\n', null],
365
+ ['a block-list reference', '---\nread:\n - Admin\n---\n', '---\nread:\n - Admins\n---\n'],
366
+ ['a flow-YAML reference', FLOW_ACCESS_MD, '---\nread: ["Admins"]\n---\nwrite:\n - Admins\n'],
367
+ ])('a file the renaming person cannot read never blocks the rename (%s)', async (_label, secret, expected) => {
368
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-refscan-hidden-'));
369
+ try {
370
+ const repo = path.join(root, KB);
371
+ await write(repo, 'Team/access.md', '---\nread:\n - Admin\n---\n');
372
+ await write(repo, 'Secret/access.md', secret);
373
+ const scanner = new KbReferenceScanner(stubWorkspace(root), new NodeFs(), KB);
374
+ const onlyTeam: PathVisibility = async (paths) => new Map(paths.map((p) => [p, p.startsWith('Team/')]));
375
+ const writes = await scanner.rewriteReferences('ws-1', 'admin', 'Admins', (m) => new Error(m), onlyTeam);
376
+ const byPath = new Map(writes.map((w) => [w.repoRelativePath, w.content]));
377
+ expect(byPath.get('Team/access.md')).toBe('---\nread:\n - Admins\n---\n');
378
+ expect(byPath.get('Secret/access.md') ?? null).toBe(expected);
379
+ } finally {
380
+ await fs.rm(root, { recursive: true, force: true });
381
+ }
382
+ });
383
+
384
+ it('a rename by someone who can read nothing still completes', async () => {
385
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-refscan-none-visible-'));
386
+ try {
387
+ await write(path.join(root, KB), 'Team/access.md', FLOW_ACCESS_MD);
388
+ const scanner = new KbReferenceScanner(stubWorkspace(root), new NodeFs(), KB);
389
+ const writes = await scanner.rewriteReferences('ws-1', 'admin', 'Admins', (m) => new Error(m), NONE_VISIBLE);
390
+ expect(writes.map((w) => w.repoRelativePath)).toEqual(['Team/access.md']);
391
+ } finally {
392
+ await fs.rm(root, { recursive: true, force: true });
393
+ }
394
+ });
395
+ });
396
+
397
+ describe('KbReferenceScanner — a folder the walk cannot list', () => {
398
+ /** The real walk, reporting `Locked` as a folder it could not list. */
399
+ function walkerWithHole(hole = 'Locked'): ITreeWalker & { walks: number } {
400
+ const disk = new NodeFs();
401
+ const walker = {
402
+ walks: 0,
403
+ walk: (r: string, o: Parameters<NodeFs['walk']>[1], l: Parameters<NodeFs['walk']>[2]) => disk.walk(r, o, l),
404
+ walkFiles: (r: string, m: (b: string) => boolean, o?: { strict?: boolean }) => disk.walkFiles(r, m, o),
405
+ walkKb: async (r: string, l: Parameters<NodeFs['walkKb']>[1], o?: Parameters<NodeFs['walkKb']>[2]) => {
406
+ walker.walks++;
407
+ const result = await disk.walkKb(r, l, o);
408
+ return { holes: [...result.holes, hole] };
409
+ },
410
+ };
411
+ return walker;
412
+ }
413
+
414
+ it('a rename refuses with nothing written: the folder may hold references', async () => {
415
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-refscan-hole-'));
416
+ try {
417
+ await write(path.join(root, KB), 'team/access.md', '---\nread:\n - Sales\n---\n');
418
+ const scanner = new KbReferenceScanner(stubWorkspace(root), walkerWithHole(), KB);
419
+ await expect(
420
+ scanner.rewriteReferences('ws-1', 'sales', 'Revenue', (m) => new Error(m), ALL_VISIBLE),
421
+ ).rejects.toThrow(UNSHOWN_REFERENCE_REFUSAL);
422
+ } finally {
423
+ await fs.rm(root, { recursive: true, force: true });
424
+ }
425
+ });
426
+
427
+ // The folder's own access.md may be as unreadable as its listing, so the
428
+ // visibility check cannot see the rules that close it: never named.
429
+ it('the refusal neither names nor counts the folder, even to someone who could read every path', async () => {
430
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-refscan-hole-hidden-'));
431
+ try {
432
+ await write(path.join(root, KB), 'team/access.md', '---\nread:\n - Sales\n---\n');
433
+ const scanner = new KbReferenceScanner(stubWorkspace(root), walkerWithHole(), KB);
434
+ const err = await scanner
435
+ .rewriteReferences('ws-1', 'sales', 'Revenue', (m) => new Error(m), ALL_VISIBLE)
436
+ .catch((e: Error) => e);
437
+ expect((err as Error).message).toBe(UNSHOWN_REFERENCE_REFUSAL);
438
+ } finally {
439
+ await fs.rm(root, { recursive: true, force: true });
440
+ }
441
+ });
442
+
443
+ it('a folder name with a line break cannot forge a second line in the refusal', async () => {
444
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-refscan-hole3-'));
445
+ try {
446
+ await write(path.join(root, KB), 'team/access.md', '---\nread:\n - Sales\n---\n');
447
+ const scanner = new KbReferenceScanner(stubWorkspace(root), walkerWithHole('Lo\nFAKE: line'), KB);
448
+ const err = await scanner
449
+ .rewriteReferences('ws-1', 'sales', 'Revenue', (m) => new Error(m), ALL_VISIBLE)
450
+ .catch((e: Error) => e);
451
+ expect((err as Error).message).not.toContain('\n');
452
+ expect((err as Error).message).not.toContain('FAKE');
453
+ } finally {
454
+ await fs.rm(root, { recursive: true, force: true });
455
+ }
456
+ });
457
+
458
+ it('a file the rewrite cannot read is neither named nor counted, and its fs cause stays out of the error', async () => {
459
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-refscan-unreadable-'));
460
+ try {
461
+ const repo = path.join(root, KB);
462
+ await write(repo, 'team/access.md', '---\nread:\n - Sales\n---\n');
463
+ // The walk lists team/Ghost.md, but there is no file to read.
464
+ const disk = new NodeFs();
465
+ const walker: ITreeWalker = {
466
+ walk: (r, o, l) => disk.walk(r, o, l),
467
+ walkFiles: (r, m, o) => disk.walkFiles(r, m, o),
468
+ walkKb: async (r, l, o) => {
469
+ const result = await disk.walkKb(r, l, o);
470
+ for (const listener of l) listener.onFile?.('team', 'Ghost.md');
471
+ return result;
472
+ },
473
+ };
474
+ const scanner = new KbReferenceScanner(stubWorkspace(root), walker, KB);
475
+ const causes: (string | undefined)[] = [];
476
+ const err = await scanner
477
+ .rewriteReferences(
478
+ 'ws-1',
479
+ 'sales',
480
+ 'Revenue',
481
+ (m, cause) => {
482
+ causes.push(cause);
483
+ return new Error(m);
484
+ },
485
+ ALL_VISIBLE,
486
+ )
487
+ .catch((e: Error) => e);
488
+ expect((err as Error).message).toBe(UNSHOWN_REFERENCE_REFUSAL);
489
+ expect(causes).toEqual([undefined]);
490
+ } finally {
491
+ await fs.rm(root, { recursive: true, force: true });
492
+ }
493
+ });
494
+
495
+ it('the advisory scan still answers what it read, but never caches an incomplete result', async () => {
496
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-refscan-hole2-'));
497
+ try {
498
+ await write(path.join(root, KB), 'team/access.md', '---\nread:\n - Sales\n---\n');
499
+ const walker = walkerWithHole();
500
+ const scanner = new KbReferenceScanner(stubWorkspace(root), walker, KB);
501
+ expect((await scanner.scan('ws-1')).get('sales')).toHaveLength(1);
502
+ await scanner.scan('ws-1');
503
+ // Not served from cache: the second scan retried the listing.
504
+ expect(walker.walks).toBe(2);
505
+ } finally {
506
+ await fs.rm(root, { recursive: true, force: true });
507
+ }
508
+ });
509
+
510
+ it('a scan that could not read a candidate file is not cached', async () => {
511
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-refscan-unread-'));
512
+ try {
513
+ await write(path.join(root, KB), 'team/access.md', '---\nread:\n - Sales\n---\n');
514
+ // The walk lists team/Ghost.md, but there is no file to read.
515
+ const disk = new NodeFs();
516
+ let walks = 0;
517
+ const walker: ITreeWalker = {
518
+ walk: (r, o, l) => disk.walk(r, o, l),
519
+ walkFiles: (r, m, o) => disk.walkFiles(r, m, o),
520
+ walkKb: async (r, l, o) => {
521
+ walks++;
522
+ const result = await disk.walkKb(r, l, o);
523
+ for (const listener of l) listener.onFile?.('team', 'Ghost.md');
524
+ return result;
525
+ },
526
+ };
527
+ const scanner = new KbReferenceScanner(stubWorkspace(root), walker, KB);
528
+ expect((await scanner.scan('ws-1')).get('sales')).toHaveLength(1);
529
+ await scanner.scan('ws-1');
530
+ // Not served from cache: the second scan walked and read again.
531
+ expect(walks).toBe(2);
532
+ } finally {
533
+ await fs.rm(root, { recursive: true, force: true });
534
+ }
535
+ });
536
+ });
537
+
187
538
  describe('KbReferenceScanner — event-bus cache invalidation', () => {
188
539
  it('drops the cached scan when a scanned-extension file changes in the workspace', async () => {
189
540
  const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-refscan-bus-'));
@@ -192,7 +543,7 @@ describe('KbReferenceScanner — event-bus cache invalidation', () => {
192
543
  await write(repo, 'team/access.md', '---\nread:\n - Sales\n---\n');
193
544
  const listeners: ((e: { kind: string; workspaceId?: string; path?: string }) => void)[] = [];
194
545
  const bus = { onEmit: (l: (typeof listeners)[number]) => (listeners.push(l), () => undefined) };
195
- const scanner = new KbReferenceScanner(stubWorkspace(root), KB, bus);
546
+ const scanner = new KbReferenceScanner(stubWorkspace(root), new NodeFs(), KB, bus);
196
547
 
197
548
  expect((await scanner.scan('ws-1')).get('sales')).toHaveLength(1);
198
549
 
@@ -216,22 +567,23 @@ describe('KbReferenceScanner — event-bus cache invalidation', () => {
216
567
  try {
217
568
  const repo = path.join(root, KB);
218
569
  await write(repo, 'team/access.md', '---\nread:\n - Sales\n---\n');
219
- // The workspace stub invalidates DURING the first scan's tree listing —
570
+ // The walker invalidates DURING the first scan's tree listing —
220
571
  // modelling a write that lands after the scan started reading.
221
- const base = stubWorkspace(root);
572
+ const disk = new NodeFs();
222
573
  let armed = true;
223
- const ws = {
224
- ...base,
225
- listFiles: async (id: string) => {
226
- const tree = await base.listFiles(id);
574
+ const walker: ITreeWalker = {
575
+ walk: (r, o, l) => disk.walk(r, o, l),
576
+ walkFiles: (r, m, o) => disk.walkFiles(r, m, o),
577
+ walkKb: async (r, l, o) => {
578
+ const result = await disk.walkKb(r, l, o);
227
579
  if (armed) {
228
580
  armed = false;
229
581
  scanner.invalidate('ws-1');
230
582
  }
231
- return tree;
583
+ return result;
232
584
  },
233
- } as unknown as IWorkspaceService;
234
- const scanner = new KbReferenceScanner(ws, KB);
585
+ };
586
+ const scanner = new KbReferenceScanner(stubWorkspace(root), walker, KB);
235
587
 
236
588
  // The caller still gets the snapshot it asked for...
237
589
  expect((await scanner.scan('ws-1')).get('sales')).toHaveLength(1);
@@ -252,7 +604,7 @@ describe('KbReferenceScanner — event-bus cache invalidation', () => {
252
604
  await write(repo, 'team/access.md', '---\nread:\n - Sales\n---\n');
253
605
  const listeners: ((e: { kind: string; workspaceId?: string; path?: string }) => void)[] = [];
254
606
  const bus = { onEmit: (l: (typeof listeners)[number]) => (listeners.push(l), () => undefined) };
255
- const scanner = new KbReferenceScanner(stubWorkspace(root), KB, bus);
607
+ const scanner = new KbReferenceScanner(stubWorkspace(root), new NodeFs(), KB, bus);
256
608
 
257
609
  const first = await scanner.scan('ws-1');
258
610
  for (const l of listeners) {
@@ -272,7 +624,7 @@ describe('KbReferenceScanner — event-bus cache invalidation', () => {
272
624
  await write(repo, 'team/access.md', '---\nread:\n - Sales\n---\n');
273
625
  const listeners: ((e: { kind: string; workspaceId?: string; path?: string }) => void)[] = [];
274
626
  const bus = { onEmit: (l: (typeof listeners)[number]) => (listeners.push(l), () => undefined) };
275
- const scanner = new KbReferenceScanner(stubWorkspace(root), KB, bus);
627
+ const scanner = new KbReferenceScanner(stubWorkspace(root), new NodeFs(), KB, bus);
276
628
 
277
629
  const first = await scanner.scan('ws-1');
278
630
  for (const l of listeners) {
@@ -127,7 +127,7 @@ describe('SECURITY: Admin-role removal takes effect on the next request', () =>
127
127
  // cache and could pass this test while production still served stale
128
128
  // verdicts, which is the failure mode worth guarding.
129
129
  access = new AccessControlService(ws, KB, new NodeFs(), [OWNER]);
130
- rolesAdmin = new RolesAdminService(ws, stubWorkflow(), access, KB, () => DEFAULT_BRANCH, undefined, [
130
+ rolesAdmin = new RolesAdminService(ws, stubWorkflow(), access, new NodeFs(), KB, () => DEFAULT_BRANCH, undefined, [
131
131
  OWNER,
132
132
  ]);
133
133
  const adminAccess = new AdminAccessService(access, ws, DEFAULT_BRANCH, [OWNER]);
@@ -21,6 +21,8 @@ const WS = workspaceIdForBranch(DEFAULT_BRANCH);
21
21
 
22
22
  const ADMIN: AuthUser = { id: 'u-admin', email: 'razvan@bevel.software', name: 'Razvan' } as AuthUser;
23
23
  const OTHER_ADMIN: AuthUser = { id: 'u-2', email: 'juan@bevel.software', name: 'Juan' } as AuthUser;
24
+ /** A Sales member: reads the Sales-governed files the roster tests reference. */
25
+ const SALES_VIEWER = 'felix@example.com';
24
26
 
25
27
  const ROLES = `roles:
26
28
  Admin:
@@ -169,7 +171,7 @@ describe('RolesAdminService', () => {
169
171
  access = new AccessControlService(ws, KB, new NodeFs());
170
172
  workflow = stubWorkflow();
171
173
  bus = stubEventBus();
172
- svc = new RolesAdminService(ws, workflow.svc, access, KB, () => DEFAULT_BRANCH, bus.bus, [
174
+ svc = new RolesAdminService(ws, workflow.svc, access, new NodeFs(), KB, () => DEFAULT_BRANCH, bus.bus, [
173
175
  'recovery-admin@example.com',
174
176
  ]);
175
177
  });
@@ -179,7 +181,7 @@ describe('RolesAdminService', () => {
179
181
  });
180
182
 
181
183
  it('getRoster lists roles, marks Admin, dedupes members', async () => {
182
- const roster = await svc.getRoster();
184
+ const roster = await svc.getRoster(ADMIN.email);
183
185
  expect(roster.map((r) => r.canonical)).toEqual(['admin', 'sales']);
184
186
  expect(roster.find((r) => r.canonical === 'admin')!.isAdmin).toBe(true);
185
187
  expect(roster.find((r) => r.canonical === 'sales')!.members).toEqual(['felix@example.com']);
@@ -191,9 +193,9 @@ describe('RolesAdminService', () => {
191
193
  // `DEFAULT_BRANCH` and froze it — every roles route then failed branch
192
194
  // validation with `Invalid branch name "": empty name` until a restart.
193
195
  let branch = '';
194
- const late = new RolesAdminService(ws, workflow.svc, access, KB, () => branch, bus.bus);
196
+ const late = new RolesAdminService(ws, workflow.svc, access, new NodeFs(), KB, () => branch, bus.bus);
195
197
  branch = DEFAULT_BRANCH;
196
- const roster = await late.getRoster();
198
+ const roster = await late.getRoster(ADMIN.email);
197
199
  expect(roster.map((r) => r.canonical)).toEqual(['admin', 'sales']);
198
200
  });
199
201
 
@@ -255,7 +257,7 @@ describe('RolesAdminService', () => {
255
257
  const corrupt = 'roles:\n Admin:\n - a@x.eu\n Admin:\n - b@x.eu\n';
256
258
  await write(repo, 'roles.yaml', corrupt);
257
259
  access.invalidate(WS);
258
- const adminless = new RolesAdminService(ws, workflow.svc, access, KB, () => DEFAULT_BRANCH, bus.bus);
260
+ const adminless = new RolesAdminService(ws, workflow.svc, access, new NodeFs(), KB, () => DEFAULT_BRANCH, bus.bus);
259
261
  // 409, not 500: the route helper hides >=500 bodies behind a generic
260
262
  // message, and this break-glass refusal must reach the operator verbatim.
261
263
  await expect(adminless.recover(ADMIN)).rejects.toMatchObject({
@@ -273,13 +275,24 @@ describe('RolesAdminService', () => {
273
275
  // ...AND a grant that lives ONLY in a node's own frontmatter (the gap).
274
276
  await write(repo, 'team/deal.md', '---\nowner: Sales\n---\n# Deal\n');
275
277
 
276
- const sales = (await svc.getRoster()).find((r) => r.canonical === 'sales')!;
278
+ const sales = (await svc.getRoster(SALES_VIEWER)).find((r) => r.canonical === 'sales')!;
277
279
  const paths = sales.referencedBy.map((r) => r.path).sort();
278
280
  expect(paths).toEqual(['team/access.md', 'team/deal.md']);
279
281
  // The frontmatter ref carries its verb, so the warning can name it.
280
282
  expect(sales.referencedBy).toContainEqual({ path: 'team/deal.md', verb: 'owner' });
281
283
  // An unreferenced role still shows nothing.
282
- expect((await svc.getRoster()).find((r) => r.canonical === 'admin')!.referencedBy).toEqual([]);
284
+ expect((await svc.getRoster(SALES_VIEWER)).find((r) => r.canonical === 'admin')!.referencedBy).toEqual([]);
285
+ });
286
+
287
+ it("getRoster names only files the viewer can read and leaves the rest out, uncounted — an Admin can't read Sales's files", async () => {
288
+ await write(repo, 'team/access.md', '---\nread:\n - deny everyone\n - Sales\n---\n');
289
+ await write(repo, 'team/deal.md', '---\nowner: Sales\n---\n# Deal\n');
290
+
291
+ const asAdmin = (await svc.getRoster(ADMIN.email)).find((r) => r.canonical === 'sales')!;
292
+ expect(asAdmin.referencedBy).toEqual([]);
293
+ expect(asAdmin).not.toHaveProperty('referencesHidden');
294
+ const asMember = (await svc.getRoster(SALES_VIEWER)).find((r) => r.canonical === 'sales')!;
295
+ expect(asMember.referencedBy).toHaveLength(2);
283
296
  });
284
297
 
285
298
  it('addMember persists + is idempotent', async () => {
@@ -560,6 +573,7 @@ describe('roster referencedBy under group shadowing', () => {
560
573
  ws,
561
574
  stubWorkflow().svc,
562
575
  new AccessControlService(ws, KB, new NodeFs()),
576
+ new NodeFs(),
563
577
  KB,
564
578
  () => DEFAULT_BRANCH,
565
579
  stubEventBus().bus,
@@ -570,20 +584,26 @@ describe('roster referencedBy under group shadowing', () => {
570
584
  await fs.rm(root, { recursive: true, force: true });
571
585
  });
572
586
 
573
- it('a bare-token hit is the GROUP’s when a group shadows the role name; role/<name> hits stay the role’s', async () => {
574
- // A group named Sales shadows the Sales role: bare tokens resolve to the
575
- // GROUP, so the roles roster must not claim them as role references.
587
+ it('a bare-token hit stays the ROLE’s when a group shares the name; group/<name> hits never are', async () => {
588
+ // A group named Sales shares the Sales role's name: bare tokens still
589
+ // resolve to the ROLE, so the roles roster claims them; group/ is the group.
576
590
  await write(repo, 'groups.yaml', 'groups:\n Sales:\n - pat@x.io\n');
577
- await write(repo, 'team/access.md', '---\nread:\n - Sales\nwrite:\n - role/Sales\n---\n');
591
+ await write(
592
+ repo,
593
+ 'team/access.md',
594
+ '---\nread:\n - Sales\nwrite:\n - role/Sales\nowner:\n - group/Sales\n---\n',
595
+ );
578
596
 
579
- const sales = (await svc.getRoster()).find((r) => r.canonical === 'sales')!;
580
- // Only the explicit spelling counts for the ROLE.
581
- expect(sales.referencedBy).toEqual([{ path: 'team/access.md', verb: 'write' }]);
597
+ const sales = (await svc.getRoster(SALES_VIEWER)).find((r) => r.canonical === 'sales')!;
598
+ expect(sales.referencedBy).toEqual([
599
+ { path: 'team/access.md', verb: 'read' },
600
+ { path: 'team/access.md', verb: 'write' },
601
+ ]);
582
602
  });
583
603
 
584
- it('unshadowed: bare hits still count for the role (both spellings)', async () => {
604
+ it('with no group of the name: bare hits count for the role (both spellings)', async () => {
585
605
  await write(repo, 'team/access.md', '---\nread:\n - Sales\nwrite:\n - role/Sales\n---\n');
586
- const sales = (await svc.getRoster()).find((r) => r.canonical === 'sales')!;
606
+ const sales = (await svc.getRoster(SALES_VIEWER)).find((r) => r.canonical === 'sales')!;
587
607
  expect(sales.referencedBy).toEqual([
588
608
  { path: 'team/access.md', verb: 'read' },
589
609
  { path: 'team/access.md', verb: 'write' },
@@ -611,6 +631,7 @@ describe('Admin fixed members — the deployment admin', () => {
611
631
  ws,
612
632
  workflow.svc,
613
633
  new AccessControlService(ws, KB, new NodeFs()),
634
+ new NodeFs(),
614
635
  KB,
615
636
  () => DEFAULT_BRANCH,
616
637
  stubEventBus().bus,
@@ -632,7 +653,7 @@ describe('Admin fixed members — the deployment admin', () => {
632
653
  });
633
654
 
634
655
  it('lists the deployment admin as a fixed member of Admin, and of no other role', async () => {
635
- const roster = await serviceWith([OWNER]).getRoster();
656
+ const roster = await serviceWith([OWNER]).getRoster(ADMIN.email);
636
657
  expect(roster.find((r) => r.canonical === 'admin')!.fixedMembers).toEqual([OWNER]);
637
658
  expect(roster.find((r) => r.canonical === 'sales')!.fixedMembers).toEqual([]);
638
659
  });
@@ -641,7 +662,7 @@ describe('Admin fixed members — the deployment admin', () => {
641
662
  // The page prints `fixedMembersNote` as sent, so this is the one copy of
642
663
  // the wording: the note and the API refusal cannot drift apart.
643
664
  const svc = serviceWith([OWNER]);
644
- const roster = await svc.getRoster();
665
+ const roster = await svc.getRoster(ADMIN.email);
645
666
  const note = roster.find((r) => r.canonical === 'admin')!.fixedMembersNote;
646
667
  expect(note).toBe('Owner; cannot be removed here');
647
668
  expect(roster.find((r) => r.canonical === 'sales')!.fixedMembersNote).toBeNull();
@@ -652,10 +673,10 @@ describe('Admin fixed members — the deployment admin', () => {
652
673
  it('canonicalises the configured address and drops a blank one', async () => {
653
674
  // The composition root passes `[config.adminEmail]` unconditionally, so an
654
675
  // unset ADMIN_EMAIL arrives as [''] — that must not become a member "".
655
- expect((await serviceWith(['']).getRoster()).find((r) => r.canonical === 'admin')!.fixedMembers)
676
+ expect((await serviceWith(['']).getRoster(ADMIN.email)).find((r) => r.canonical === 'admin')!.fixedMembers)
656
677
  .toEqual([]);
657
678
  expect(
658
- (await serviceWith([' Owner@Bevel.Software ']).getRoster()).find((r) => r.canonical === 'admin')!
679
+ (await serviceWith([' Owner@Bevel.Software ']).getRoster(ADMIN.email)).find((r) => r.canonical === 'admin')!
659
680
  .fixedMembers,
660
681
  ).toEqual([OWNER]);
661
682
  });
@@ -664,7 +685,7 @@ describe('Admin fixed members — the deployment admin', () => {
664
685
  // The seeded roles.yaml names ADMIN_EMAIL, so overlap is the normal case.
665
686
  // `members` must keep it — the Admin >=1-direct-email invariant counts the
666
687
  // FILE — and the page is what renders the overlap once.
667
- const admin = (await serviceWith(['razvan@bevel.software']).getRoster()).find(
688
+ const admin = (await serviceWith(['razvan@bevel.software']).getRoster(ADMIN.email)).find(
668
689
  (r) => r.canonical === 'admin',
669
690
  )!;
670
691
  expect(admin.members).toContain('razvan@bevel.software');
@@ -710,7 +731,7 @@ describe('Admin fixed members — the deployment admin', () => {
710
731
  });
711
732
 
712
733
  it('with no ADMIN_EMAIL configured, no role has a fixed member', async () => {
713
- const roster = await serviceWith([]).getRoster();
734
+ const roster = await serviceWith([]).getRoster(ADMIN.email);
714
735
  expect(roster.every((r) => r.fixedMembers.length === 0)).toBe(true);
715
736
  expect(roster.every((r) => r.fixedMembersNote === null)).toBe(true);
716
737
  // ...and ordinary Admin membership stays editable.