@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
@@ -3,9 +3,15 @@
3
3
  * services (roles + groups) drive for "which access rules name this
4
4
  * principal" and "rewrite every reference atomically":
5
5
  *
6
- * - `findRoleRefsInText` / `rewriteRoleTokensInText`: the config-region
7
- * parse that decides what IS a principal reference. One source of truth,
8
- * so the delete/rename warning and the rename rewrite can never disagree.
6
+ * - `resolverRoleRefs`: what IS a principal reference — the resolver's own
7
+ * parse of each file, so the reports name every enforced rule.
8
+ * - `findRoleRefsInText` / `rewriteRoleTokensInText`: the line-level walk
9
+ * the rename edits text with. It handles block lists and `key: value`
10
+ * entries; quoted and flow-YAML own frontmatter is rewritten by the
11
+ * full parser's own tree (`rewriteOwnFrontmatterStructurally`). The
12
+ * rewrite re-checks its result with `resolverRoleRefs` and refuses
13
+ * when a reference survives, so the warning and the rename can never
14
+ * disagree.
9
15
  * - `KbReferenceScanner`: candidate collection (every KB file with an
10
16
  * access-frontmatter extension — `.md` and `.tool`, the resolver's own
11
17
  * set), the advisory reference scan (cached — roster mutations must not
@@ -14,24 +20,37 @@
14
20
  * file's ORIGINAL text too, so rollback snapshots reuse the read the
15
21
  * rewrite already did instead of a second full-KB pass).
16
22
  *
17
- * Tokens here are the ENTRY-GRAMMAR canonical tokens: a bare name (group
18
- * first, then role) or an explicit `role/<name>`. Callers translate: a ROLE's
19
- * references are its bare token plus its `role/` token; a GROUP's are its
20
- * bare token only.
23
+ * Tokens here are the ENTRY-GRAMMAR canonical tokens: a bare name (the role
24
+ * when one exists, else the group) or an explicit `role/<name>` /
25
+ * `group/<name>`. Callers translate: a ROLE's references are its bare token
26
+ * plus its `role/` token; a GROUP's are its `group/` token, plus the bare
27
+ * token while no role shares the name.
21
28
  */
22
29
 
23
30
  import path from 'node:path';
24
31
  import { promises as fs } from 'node:fs';
25
32
 
26
- import { isFrontmatterFence, type FileTreeEntry, type IWorkspaceService } from '@bevel-software/platform-shared';
33
+ import { Scalar, YAMLSeq, isAlias, isMap, isNode, isScalar, isSeq, parseDocument, type Document } from 'yaml';
34
+ import { isFrontmatterFence, type IWorkspaceService } from '@bevel-software/platform-shared';
35
+ import type { ITreeWalker } from '../../shared/fs.contract.js';
36
+ import { logger } from '../../shared/logging.js';
37
+ import { printable, sanitizedPath } from '../../shared/printable.js';
27
38
  import {
39
+ AccessDeniedError,
28
40
  KNOWN_VERBS,
29
41
  accessMdDeclaresBodyRules,
42
+ accessMdSelfEntries,
43
+ canonicalRoleName,
30
44
  hasAccessFrontmatterExtension,
31
45
  isAccessMdPath,
32
46
  parseAccessEntry,
47
+ parseAccessFile,
48
+ parseOwnAccessEntries,
33
49
  stripComment,
34
50
  } from '../access-model/index.js';
51
+ import type { ParsedEntry, Verb } from '../access-model/index.js';
52
+
53
+ const log = logger('access');
35
54
 
36
55
  /**
37
56
  * Resolve the [start, end) line range that role rewrites may touch — the YAML
@@ -237,8 +256,8 @@ function walkRoleRefs(
237
256
 
238
257
  /**
239
258
  * Every genuine role/group reference in `text`'s rule regions, as
240
- * {role, verb} where `role` is the canonical ENTRY TOKEN (bare name or
241
- * `role/<name>`). `isAccessMd` marks an `access.md` file, whose BODY is a
259
+ * {role, verb} where `role` is the canonical ENTRY TOKEN (bare name,
260
+ * `role/<name>` or `group/<name>`). `isAccessMd` marks an `access.md` file, whose BODY is a
242
261
  * rule source in the body-governed format — see {@link ruleLineRanges}.
243
262
  */
244
263
  export function findRoleRefsInText(
@@ -291,6 +310,115 @@ export function rewriteRoleTokensInText(
291
310
  return changed ? lines.join('\n') : text;
292
311
  }
293
312
 
313
+ /**
314
+ * The structural rewrite, for the references the line walk cannot edit:
315
+ * a file's OWN frontmatter is read by the full YAML parser whenever it holds
316
+ * flow sequences, quoted scalars or other real YAML (`ownEntriesRoot` in the
317
+ * grammar), and the line walk only knows block lists and `key: value`. So
318
+ * this edits the same document the resolver reads — the frontmatter's
319
+ * top-level verb keys, by the full parser's own node tree with the same
320
+ * FAILSAFE schema — and replaces each scalar entry whose token is in
321
+ * `oldTokens`, keeping its quoting, its comments and every other node. An
322
+ * alias the resolver reads as a verb's entries is never edited through: one
323
+ * that names an old token is replaced by a plain scalar, and one that is the
324
+ * verb's whole list (`read: *team`, `&team` a list) by a rewritten copy of
325
+ * that list, so the anchor it points at (which may sit under any other key)
326
+ * is left alone.
327
+ *
328
+ * The other rule sources — a legacy `access.md`'s frontmatter and a
329
+ * body-governed one's body — are read by the YAML-subset parser only, which
330
+ * the line walk mirrors, so they never need this.
331
+ */
332
+ function rewriteOwnFrontmatterStructurally(
333
+ text: string,
334
+ oldTokens: ReadonlySet<string>,
335
+ newDisplayName: string,
336
+ ): string {
337
+ const lines = text.split('\n');
338
+ const { start, end } = configLineRange(lines, true);
339
+ if (start >= end) return text;
340
+ // Typed as an editable document: the rewrite puts new, unparsed scalars in it.
341
+ const doc: Document = parseDocument(lines.slice(start, end).join('\n'), { schema: 'failsafe' });
342
+ // A document the full parser rejects is not what the resolver read: it fell
343
+ // back to the subset parser, whose reading the line walk already covers.
344
+ if (doc.errors.length > 0 || !isMap(doc.contents)) return text;
345
+ let changed = false;
346
+ const rewritten = (node: unknown): Scalar | null => {
347
+ const target = isAlias(node) ? node.resolve(doc) : node;
348
+ if (!isScalar(target) || typeof target.value !== 'string') return null;
349
+ const parsed = parseAccessEntry(target.value);
350
+ if (!parsed.ok || parsed.entry.kind !== 'role' || !oldTokens.has(parsed.entry.role)) return null;
351
+ const value = `${parsed.entry.deny ? 'deny ' : ''}${newDisplayName}`;
352
+ changed = true;
353
+ if (isAlias(node)) return new Scalar(value);
354
+ target.value = value;
355
+ return target;
356
+ };
357
+ // The anchored list an alias names, as a rewritten copy — or null when
358
+ // none of its entries changes, so the alias stays. Each item is cloned
359
+ // before it is rewritten (the anchor's own items stay as they are) and
360
+ // loses any anchor of its own (YAML allows one definition of a name).
361
+ const rewrittenCopy = (seq: YAMLSeq): YAMLSeq | null => {
362
+ let hit = false;
363
+ const copy = new YAMLSeq(doc.schema);
364
+ copy.flow = seq.flow;
365
+ copy.items = seq.items.map((item) => {
366
+ const clone = isNode(item) ? item.clone() : item;
367
+ if (isScalar(clone)) clone.anchor = undefined;
368
+ const r = rewritten(clone);
369
+ if (r) hit = true;
370
+ return r ?? clone;
371
+ });
372
+ return hit ? copy : null;
373
+ };
374
+ for (const pair of doc.contents.items) {
375
+ const key = isScalar(pair.key) ? pair.key.value : pair.key;
376
+ if (typeof key !== 'string' || !VERB_KEYS.has(key)) continue;
377
+ const aliased = isAlias(pair.value) ? pair.value.resolve(doc) : null;
378
+ if (isSeq(aliased)) {
379
+ const copy = rewrittenCopy(aliased);
380
+ if (copy) pair.value = copy;
381
+ } else if (isSeq(pair.value)) {
382
+ pair.value.items = pair.value.items.map((item) => rewritten(item) ?? item);
383
+ } else {
384
+ const scalar = rewritten(pair.value);
385
+ if (scalar) pair.value = scalar;
386
+ }
387
+ }
388
+ if (!changed) return text;
389
+ // Never fold a long value onto a second line, nor pad a flow list.
390
+ const frontmatter = doc.toString({ lineWidth: 0, flowCollectionPadding: false }).replace(/\n$/, '');
391
+ return [...lines.slice(0, start), frontmatter, ...lines.slice(end)].join('\n');
392
+ }
393
+
394
+ /**
395
+ * The principal references the RESOLVER reads from one candidate file, by
396
+ * the resolver's own parsers: an `access.md`'s folder rules
397
+ * (`parseAccessFile`) and its self-rules (`accessMdSelfEntries`), any other
398
+ * file's own frontmatter (`parseOwnAccessEntries`). Those parsers read
399
+ * quoted scalars and flow sequences (`read: ["Admin"]`) the line walker
400
+ * above does not, so the reports built on this name every rule that is
401
+ * actually enforced — and nothing the resolver ignores (a broken
402
+ * `access.md` is dropped from the model, so it references nothing).
403
+ */
404
+ export function resolverRoleRefs(text: string, repoRel: string): { role: string; verb: string }[] {
405
+ const out: { role: string; verb: string }[] = [];
406
+ const add = (entries: Record<Verb, ParsedEntry[]> | null): void => {
407
+ if (!entries) return;
408
+ for (const verb of KNOWN_VERBS) {
409
+ for (const e of entries[verb]) if (e.kind === 'role') out.push({ role: e.role, verb });
410
+ }
411
+ };
412
+ if (isAccessMdPath(repoRel)) {
413
+ const folder = parseAccessFile(text, repoRel);
414
+ if (folder.ok) add(folder.file.entries);
415
+ add(accessMdSelfEntries(text));
416
+ } else {
417
+ add(parseOwnAccessEntries(text));
418
+ }
419
+ return out;
420
+ }
421
+
294
422
  export interface ReferenceHit {
295
423
  path: string;
296
424
  verb: string;
@@ -304,6 +432,75 @@ export interface ReferenceRewrite {
304
432
  original: string;
305
433
  }
306
434
 
435
+ /**
436
+ * Which of the given repo-relative paths the person a refusal is for may
437
+ * read — `canReadBatch` bound to that person. A path is itself a disclosure,
438
+ * so a refusal names only these and says nothing of the rest.
439
+ */
440
+ export type PathVisibility = (repoRelPaths: string[]) => Promise<ReadonlyMap<string, boolean>>;
441
+
442
+ /**
443
+ * The one refusal for a rename blocked by a place the renaming person may
444
+ * not be shown — a folder the walk cannot list, a file it cannot read, or a
445
+ * file the person may not read that still names the old principal after the
446
+ * rewrite (a backstop: every form the resolver reads is rewritten). The same
447
+ * words whatever the cause: no path, no count, no hint of a reference, since
448
+ * Admin status does not grant read and each of those is a disclosure. The
449
+ * log names the place.
450
+ */
451
+ export const UNSHOWN_REFERENCE_REFUSAL =
452
+ 'This rename rewrites access rules across the knowledge base, and part of the knowledge base could not be rewritten, so it may still reference the old principal; rename aborted with no changes. The server log names where.';
453
+
454
+ /**
455
+ * The commit's own write gate refusing a rename's rewrite, translated for the
456
+ * person who asked. The gate judges every file the rewrite touches against the
457
+ * actor's WRITE rules (a rename's references reach files private to others,
458
+ * which an Admin may be unable to write), and its refusal names the file. A
459
+ * file its person may read keeps that refusal: the path and its eligible
460
+ * writers say whom to ask. A file they may not read gets
461
+ * `UNSHOWN_REFERENCE_REFUSAL` instead, so the hidden path stays unnamed; the
462
+ * log names it. Anything else comes back unchanged.
463
+ *
464
+ * The one bit left (some file the person cannot see names the old principal)
465
+ * is the hidden-blocker exception of "A Guard's Answer Is a Disclosure": an
466
+ * Admin's refused rename of a principal they manage says only that an unshown
467
+ * place blocks it.
468
+ */
469
+ export async function translateRewriteWriteRefusal(
470
+ err: unknown,
471
+ visible: PathVisibility,
472
+ makeError: (message: string) => Error,
473
+ ): Promise<unknown> {
474
+ if (!(err instanceof AccessDeniedError)) return err;
475
+ const p = err.access.path;
476
+ let shown = false;
477
+ try {
478
+ shown = (await visible([p])).get(p) === true;
479
+ } catch {
480
+ // Cannot tell whether the person may see it: say nothing of it.
481
+ }
482
+ if (shown) return err;
483
+ log.warn(`reference rewrite: the acting person may not write ${printable(p)}`);
484
+ const translated = makeError(UNSHOWN_REFERENCE_REFUSAL);
485
+ // withFileLocks reads the paths whose restore failed off the error it
486
+ // catches, to discard their partial bytes; the translation must carry them.
487
+ const unrestored = (err as { unrestoredPaths?: Set<string> }).unrestoredPaths;
488
+ if (unrestored) (translated as { unrestoredPaths?: Set<string> }).unrestoredPaths = unrestored;
489
+ return translated;
490
+ }
491
+
492
+ /**
493
+ * The rows of `refs` whose file `readable` allows. The rest are dropped
494
+ * outright, not counted: Admin status does not grant read, so even a count of
495
+ * rules in content the viewer cannot read is a disclosure.
496
+ */
497
+ export function readableReferences(
498
+ refs: readonly ReferenceHit[],
499
+ readable: ReadonlyMap<string, boolean>,
500
+ ): ReferenceHit[] {
501
+ return refs.filter((r) => readable.get(r.path) === true);
502
+ }
503
+
307
504
  /** How long a cached reference scan may serve after its load. Backstop only —
308
505
  * reference-changing writes invalidate explicitly (the admin services after
309
506
  * their own rewrites, and the event-bus tap below for everyone else's). */
@@ -354,6 +551,8 @@ export class KbReferenceScanner {
354
551
 
355
552
  constructor(
356
553
  private readonly workspaceService: IWorkspaceService,
554
+ /** The resolver's walk (see `collectCandidateFiles`), injected by the composition root. */
555
+ private readonly disk: ITreeWalker,
357
556
  private readonly kbDirName: string,
358
557
  eventBus?: ReferenceScanInvalidationBus,
359
558
  ) {
@@ -381,37 +580,40 @@ export class KbReferenceScanner {
381
580
  * resolver reads access verbs from — the shared
382
581
  * `ACCESS_FRONTMATTER_EXTENSIONS` set (`.md` AND `.tool`; a rename that
383
582
  * skipped `.tool` would strand a live frontmatter grant there). Sourced
384
- * from the workspace file tree (which already skips `.git` and honours
385
- * `.bevelignore`), filtered under the KB dir and returned bare
386
- * repo-relative. (`roles.yaml` matches no scanned extension, so it's
387
- * excluded; callers commit it separately.) Under save=share the working
388
- * tree matches the committed set, so this is the same candidate list
389
- * git-tracking would give.
583
+ * from the RESOLVER'S OWN walk (`walkKb`: dot-entries and vendored
584
+ * dependencies skipped, `.bevelignore` NOT honoured) — the resolver
585
+ * enforces an `access.md` the explorer hides, so a scan that honoured the
586
+ * ignore file would leave its rules out of the roster's reports and a
587
+ * rename would strand them. Paths are relative to the KB dir. (`roles.yaml`
588
+ * matches no scanned extension, so it's excluded; callers commit it
589
+ * separately.)
590
+ *
591
+ * `holes` names every folder the walk could not list: the candidate set
592
+ * is INCOMPLETE when it is non-empty, and each caller decides what that
593
+ * means — the rewrite refuses, the advisory scan says so and does not cache.
390
594
  */
391
- async collectCandidateFiles(workspaceId: string): Promise<string[]> {
392
- const tree = await this.workspaceService.listFiles(workspaceId);
393
- const prefix = `${this.kbDirName}/`;
595
+ async collectCandidateFiles(workspaceId: string): Promise<{ files: string[]; holes: string[] }> {
394
596
  const out: string[] = [];
395
- const visit = (node: FileTreeEntry): void => {
396
- if (node.type === 'file') {
397
- if (node.relativePath.startsWith(prefix) && hasAccessFrontmatterExtension(node.relativePath)) {
398
- out.push(node.relativePath.slice(prefix.length));
399
- }
400
- return;
401
- }
402
- for (const child of node.children ?? []) visit(child);
403
- };
404
- visit(tree);
405
- return out;
597
+ const { holes } = await this.disk.walkKb(await this.repoDir(workspaceId), [
598
+ {
599
+ onFile(dir, name) {
600
+ const rel = dir ? `${dir}/${name}` : name;
601
+ if (hasAccessFrontmatterExtension(rel)) out.push(rel);
602
+ },
603
+ },
604
+ ]);
605
+ return { files: out, holes };
406
606
  }
407
607
 
408
608
  /**
409
609
  * Sound scan of EVERY candidate file (folder `access.md` + `.md`/`.tool`
410
610
  * node frontmatter) for genuine principal references, indexed by canonical
411
- * entry TOKEN. Shares the
412
- * candidate set and the config-region parse with the rename rewrite, so the
413
- * delete warning and the rewrite see the SAME references. A file we cannot
414
- * read is skipped (this is an advisory read, not the atomic write path).
611
+ * entry TOKEN, read by the resolver's own parse ({@link resolverRoleRefs}).
612
+ * Shares the candidate set with the rename rewrite, and the rewrite checks
613
+ * its result against the same parse, so the delete warning and the rewrite
614
+ * cannot disagree on what references a principal. A file we cannot
615
+ * read is skipped (this is an advisory read, not the atomic write path),
616
+ * and the scan that skipped it is not cached.
415
617
  * Cached per workspace (see class doc); reads are batched with Promise.all
416
618
  * rather than serial per-file awaits.
417
619
  */
@@ -428,7 +630,12 @@ export class KbReferenceScanner {
428
630
  const startedUnder = flight.gen;
429
631
  try {
430
632
  const repoDir = await this.repoDir(workspaceId);
431
- const candidates = await this.collectCandidateFiles(workspaceId);
633
+ const { files: candidates, holes } = await this.collectCandidateFiles(workspaceId);
634
+ if (holes.length > 0) {
635
+ log.warn(
636
+ `reference scan: cannot list ${holes.map((h) => printable(h || '.')).join(', ')} — references there are not reported`,
637
+ );
638
+ }
432
639
  const texts = await Promise.all(
433
640
  candidates.map(async (repoRel) => {
434
641
  try {
@@ -442,7 +649,7 @@ export class KbReferenceScanner {
442
649
  candidates.forEach((repoRel, i) => {
443
650
  const text = texts[i];
444
651
  if (text === null) return;
445
- for (const ref of findRoleRefsInText(text, true, isAccessMdPath(repoRel))) {
652
+ for (const ref of resolverRoleRefs(text, repoRel)) {
446
653
  const list = byToken.get(ref.role);
447
654
  if (list) list.push({ path: repoRel, verb: ref.verb });
448
655
  else byToken.set(ref.role, [{ path: repoRel, verb: ref.verb }]);
@@ -451,7 +658,10 @@ export class KbReferenceScanner {
451
658
  // Cache only when no invalidation landed mid-scan; the caller still gets
452
659
  // this snapshot (at worst one write stale — same as the pre-scan world),
453
660
  // but a stale snapshot must never STICK for a TTL. The next call re-scans.
454
- if (flight.gen === startedUnder) {
661
+ // An incomplete scan (a folder could not be listed, or a candidate file
662
+ // could not be read) is never cached either: the next call retries it.
663
+ const unread = texts.some((t) => t === null);
664
+ if (flight.gen === startedUnder && holes.length === 0 && !unread) {
455
665
  this.cache.set(workspaceId, { loadedAt: Date.now(), byToken });
456
666
  }
457
667
  return byToken;
@@ -462,22 +672,50 @@ export class KbReferenceScanner {
462
672
  }
463
673
 
464
674
  /**
465
- * Find every genuine reference to `oldToken` and rewrite it to
466
- * `newDisplayName`. FAIL-CLOSED: a candidate we cannot read might reference
675
+ * Find every genuine reference to `oldToken` (or any of several spellings
676
+ * of one principal) and rewrite it to `newDisplayName`. FAIL-CLOSED: a candidate we cannot read might reference
467
677
  * the old token, and skipping it would commit a partial rewrite (a
468
678
  * half-renamed principal still pointed at by stragglers = silent access
469
679
  * drop) — so any read failure aborts via `makeError` with nothing written.
470
680
  * Returns the writes WITH each file's original text (rollback snapshots).
681
+ *
682
+ * The rename keeps the KB's references to a principal whole, so it rewrites
683
+ * every candidate, files its person may not read included: a file private
684
+ * to others blocks nothing here, and no answer of this method depends on
685
+ * what such a file holds. Whether the person may WRITE each rewritten file
686
+ * is the commit's gate to decide, not this method's (see
687
+ * `translateRewriteWriteRefusal`). Every form the resolver reads
688
+ * is rewritten — block lists and `key: value` by the line walk, quoted and
689
+ * flow-YAML own frontmatter by the structural rewrite — so there is no
690
+ * "cannot rewrite" answer for hidden content to give. The writes reach a
691
+ * caller only through surfaces gated on read (the roster, the event
692
+ * delivery), never through this method's answer.
693
+ *
694
+ * A reference the resolver still sees after both rewrites is refused: named
695
+ * with how to fix it when `visible` says its person may read the file, with
696
+ * `UNSHOWN_REFERENCE_REFUSAL` otherwise (no path, no count). Nothing the
697
+ * resolver reads should reach that, so the refusal is a backstop, not an
698
+ * answer the content of a hidden file can choose.
471
699
  */
472
700
  async rewriteReferences(
473
701
  workspaceId: string,
474
- oldToken: string,
702
+ oldToken: string | readonly string[],
475
703
  newDisplayName: string,
476
704
  makeError: (message: string, cause?: string) => Error,
705
+ visible: PathVisibility,
477
706
  ): Promise<ReferenceRewrite[]> {
707
+ const oldTokens = typeof oldToken === 'string' ? [oldToken] : oldToken;
478
708
  const repoDir = await this.repoDir(workspaceId);
479
709
  const writes: ReferenceRewrite[] = [];
480
- const candidates = await this.collectCandidateFiles(workspaceId);
710
+ const { files: candidates, holes } = await this.collectCandidateFiles(workspaceId);
711
+ // A folder we cannot list might hold references to the old token — same
712
+ // fail-closed rule as an unreadable file below, and never named nor
713
+ // counted, for the same reason: its own access.md may be just as
714
+ // unreadable, so `visible` could not see the rules that close it.
715
+ if (holes.length > 0) {
716
+ log.warn(`reference rewrite: cannot list ${holes.map((h) => printable(h || '.')).join(', ')}`);
717
+ throw makeError(UNSHOWN_REFERENCE_REFUSAL);
718
+ }
481
719
  const texts = await Promise.all(
482
720
  candidates.map(async (repoRel) => {
483
721
  try {
@@ -489,20 +727,55 @@ export class KbReferenceScanner {
489
727
  );
490
728
  for (const t of texts) {
491
729
  if (!t.ok) {
492
- throw makeError(
493
- `Cannot read ${t.repoRel} while rewriting references; rename aborted with no changes`,
494
- t.cause,
495
- );
730
+ // Never named, not even to a reader: `visible` decides from the
731
+ // file's own rules, and a file we cannot read has none it can see —
732
+ // an inherited grant would then disclose a path its own rules may
733
+ // close. The fs cause carries the path too, so it stays in the log.
734
+ log.warn(`reference rewrite: cannot read ${printable(t.repoRel)}`, { cause: t.cause });
735
+ throw makeError(UNSHOWN_REFERENCE_REFUSAL);
496
736
  }
497
737
  }
738
+ // Old tokens the rename must leave no reference to (the new name's own
739
+ // token is exempt: it IS the rewrite's result).
740
+ const newToken = canonicalRoleName(newDisplayName);
741
+ const gone = new Set(oldTokens.filter((t) => t !== newToken));
742
+ const stranded: string[] = [];
498
743
  candidates.forEach((repoRel, i) => {
499
744
  const entry = texts[i];
500
745
  if (!entry.ok) return; // unreachable — the loop above threw
501
- const rewritten = rewriteRoleTokensInText(entry.text, oldToken, newDisplayName, true, isAccessMdPath(repoRel));
746
+ let rewritten = entry.text;
747
+ for (const token of oldTokens) {
748
+ rewritten = rewriteRoleTokensInText(rewritten, token, newDisplayName, true, isAccessMdPath(repoRel));
749
+ }
750
+ // The line rewrite edits block-list and `key: value` entries; the
751
+ // resolver also reads quoted and flow-YAML own frontmatter, which the
752
+ // structural rewrite edits by the full parser's own tree.
753
+ const stillNamed = (t: string) => resolverRoleRefs(t, repoRel).some((r) => gone.has(r.role));
754
+ if (stillNamed(rewritten)) {
755
+ rewritten = rewriteOwnFrontmatterStructurally(rewritten, new Set(oldTokens), newDisplayName);
756
+ }
757
+ // A reference the resolver still sees would be stranded by the rename.
758
+ if (stillNamed(rewritten)) {
759
+ stranded.push(repoRel);
760
+ return;
761
+ }
502
762
  if (rewritten !== entry.text) {
503
763
  writes.push({ repoRelativePath: repoRel, content: rewritten, original: entry.text });
504
764
  }
505
765
  });
766
+ if (stranded.length > 0) {
767
+ const verdicts = await visible(stranded);
768
+ const shown = stranded.filter((p) => verdicts.get(p) === true);
769
+ if (shown.length < stranded.length) {
770
+ log.warn(
771
+ `reference rewrite: cannot rewrite ${stranded.map((p) => printable(p || '.')).join(', ')}`,
772
+ );
773
+ throw makeError(UNSHOWN_REFERENCE_REFUSAL);
774
+ }
775
+ throw makeError(
776
+ `${shown.map((p) => sanitizedPath(p || '.')).join(', ')} name the old principal in a form the rename cannot rewrite; rewrite it as a \`- Name\` list item, then rename again. Rename aborted with no changes`,
777
+ );
778
+ }
506
779
  return writes;
507
780
  }
508
781
 
@@ -39,15 +39,22 @@ const log = logger('roles.recover');
39
39
  import type { WorkflowEventBus } from '../workflow/index.js';
40
40
  import type { AuthUser, IWorkspaceService, IWorkflowService } from '@bevel-software/platform-shared';
41
41
  import { WorkflowDomainError } from '../../shared/domain-errors.js';
42
+ import type { ITreeWalker } from '../../shared/fs.contract.js';
42
43
  import type { IAccessControl } from './access-control.interface.js';
43
44
  import {
44
45
  ADMIN_CANONICAL,
45
46
  ROLE_TOKEN_PREFIX,
46
47
  canonicalRoleName,
47
48
  canonicalEmail,
49
+ makeRolesYamlWriteValidator,
50
+ renderRolesYaml,
51
+ GROUPS_YAML,
52
+ SYNCED_GROUPS_YAML,
53
+ validateGroupsFile,
54
+ GROUP_REF_PREFIX,
55
+ RESERVED_ROLE_NAMES,
48
56
  } from '../access-model/index.js';
49
57
  import { loadActiveGroups } from './access-control.service.js';
50
- import { makeRolesYamlWriteValidator, renderRolesYaml, GROUPS_YAML, SYNCED_GROUPS_YAML, validateGroupsFile, GROUP_REF_PREFIX, RESERVED_ROLE_NAMES } from '../access-model/index.js';
51
58
  import {
52
59
  parseRolesModel,
53
60
  deleteRole as editDeleteRole,
@@ -67,7 +74,7 @@ import {
67
74
  } from './groups-edit.js';
68
75
  import { capabilityRoleFor, isLegacyPeopleSetRole } from './capability-registry.js';
69
76
  import { AdminLockedCommits } from './admin-locked-commit.js';
70
- import { KbReferenceScanner } from './reference-scan.js';
77
+ import { KbReferenceScanner, readableReferences } from './reference-scan.js';
71
78
 
72
79
  /** A mutation that cannot proceed (invariant violation, contention, not found). */
73
80
  export class RolesAdminError extends WorkflowDomainError {
@@ -118,11 +125,11 @@ export interface RoleRosterEntry {
118
125
  capability: { description: string; groupAssignable: boolean } | null;
119
126
  /**
120
127
  * Every access rule referencing this ROLE — folder `access.md` AND node
121
- * frontmatter. `role/<name>` hits always count; a BARE-token hit counts
122
- * only when no group shadows the name (bare tokens resolve group-first, so
123
- * a shadowed bare grant is the GROUP's reference, listed on the groups
124
- * roster instead). Sound: this is the SAME scan the group rename/delete
125
- * rewrites use.
128
+ * frontmatter. `role/<name>` and BARE-token hits both count — a bare name
129
+ * is always the role when the role exists, even when a group shares the
130
+ * name (that group is `group/<name>`). Sound: this is the SAME scan the
131
+ * group rename/delete rewrites use. Only files the roster's viewer can read
132
+ * are listed (see `getRoster`).
126
133
  */
127
134
  referencedBy: { path: string; verb: string }[];
128
135
  }
@@ -180,6 +187,8 @@ export class RolesAdminService {
180
187
  private readonly workspaceService: IWorkspaceService,
181
188
  private readonly workflowService: IWorkflowService,
182
189
  private readonly accessControl: IAccessControl,
190
+ /** The composition root's walk — the one the resolver finds access rules with. */
191
+ disk: ITreeWalker,
183
192
  private readonly kbDirName: string,
184
193
  /**
185
194
  * The branch whose roles.yaml is authoritative for admin status, read
@@ -223,7 +232,7 @@ export class RolesAdminService {
223
232
  // The event bus keeps the roster's `referencedBy` fresh: a share-dialog
224
233
  // grant/revoke on any access.md happens outside this service, and the
225
234
  // scanner invalidates its cache on those writes' events (TTL as backstop).
226
- this.references = new KbReferenceScanner(workspaceService, kbDirName, eventBus);
235
+ this.references = new KbReferenceScanner(workspaceService, disk, kbDirName, eventBus);
227
236
  this.fixedAdminEmails = [
228
237
  ...new Set(deploymentAdmins.filter(Boolean).map((e) => canonicalEmail(e)).filter(Boolean)),
229
238
  ];
@@ -303,23 +312,24 @@ export class RolesAdminService {
303
312
 
304
313
  // ---- Read -------------------------------------------------------------
305
314
 
306
- /** The roster: every role on the default branch with members + references. */
307
- async getRoster(): Promise<RoleRosterEntry[]> {
315
+ /**
316
+ * The roster: every role on the default branch with members + references.
317
+ * `viewerEmail` is who the roster is for: `referencedBy` names only the
318
+ * files that viewer can read and omits the rest entirely, without a count —
319
+ * being an Admin is not read access to every file, and a path, or even a
320
+ * count of rules in unreadable content, is itself a disclosure.
321
+ */
322
+ async getRoster(viewerEmail: string): Promise<RoleRosterEntry[]> {
308
323
  const workspaceId = await this.ensureWorkspace();
309
324
  const text = await this.readRolesYaml(workspaceId);
310
325
  const model = parseRolesModel(text);
311
326
  // ONE sound scan of every candidate file (folder access.md + node
312
327
  // frontmatter) — cached in the shared scanner, so mutations returning the
313
328
  // fresh roster don't rerun the full-KB sweep — indexed by canonical entry
314
- // token. Attribution per spelling: a `role/<name>` hit is ALWAYS the
315
- // role's; a BARE hit is the role's only when no group shadows the name —
316
- // bare tokens resolve group-first, so a shadowed bare grant belongs to the
317
- // GROUP (and the groups roster attributes it there — the mirror image).
318
- const [referencesByToken, activeGroups] = await Promise.all([
319
- this.references.scan(workspaceId),
320
- loadActiveGroups((f) => this.locked.readKbFile(workspaceId, f)),
321
- ]);
322
- const groupOwnedBareNames = new Set(activeGroups.groups.keys());
329
+ // token. Attribution per spelling: a `role/<name>` hit and a BARE hit are
330
+ // both the role's — bare names resolve role-first, so a same-named group
331
+ // is only ever `group/<name>` (the groups roster's mirror image).
332
+ const referencesByToken = await this.references.scan(workspaceId);
323
333
  const out: RoleRosterEntry[] = [];
324
334
  for (const role of model) {
325
335
  const canonical = canonicalRoleName(role.displayName);
@@ -342,11 +352,18 @@ export class RolesAdminService {
342
352
  ? { description: registryEntry.description, groupAssignable: registryEntry.groupAssignable }
343
353
  : null,
344
354
  referencedBy: [
345
- ...(groupOwnedBareNames.has(canonical) ? [] : referencesByToken.get(canonical) ?? []),
355
+ ...(referencesByToken.get(canonical) ?? []),
346
356
  ...(referencesByToken.get(`${ROLE_TOKEN_PREFIX}${canonical}`) ?? []),
347
357
  ],
348
358
  });
349
359
  }
360
+ const paths = [...new Set(out.flatMap((r) => r.referencedBy.map((ref) => ref.path)))];
361
+ if (paths.length > 0) {
362
+ const readable = await this.accessControl.canReadBatch(workspaceId, viewerEmail, paths);
363
+ for (const r of out) {
364
+ r.referencedBy = readableReferences(r.referencedBy, readable);
365
+ }
366
+ }
350
367
  return out;
351
368
  }
352
369
 
@@ -450,7 +467,7 @@ export class RolesAdminService {
450
467
  );
451
468
  this.accessControl.invalidate(workspaceId);
452
469
  this.emitWrites(workspaceId, actor, [OLD_ROLES_YAML, ROLES_YAML]);
453
- return this.getRoster();
470
+ return this.getRoster(actor.email);
454
471
  }
455
472
 
456
473
  // ---- Mutations --------------------------------------------------------
@@ -484,7 +501,7 @@ export class RolesAdminService {
484
501
  ): Promise<RoleRosterEntry[]> {
485
502
  this.assertNotFixedAdmin(canonical, email);
486
503
  await this.runEdit(actor, (text) => editAddMember(text, canonical, email), opts?.aroundWrite);
487
- return this.getRoster();
504
+ return this.getRoster(actor.email);
488
505
  }
489
506
 
490
507
  /**
@@ -542,7 +559,7 @@ export class RolesAdminService {
542
559
  }
543
560
  return editRemoveMember(text, canonical, email);
544
561
  });
545
- return this.getRoster();
562
+ return this.getRoster(actor.email);
546
563
  }
547
564
 
548
565
  /**
@@ -572,12 +589,12 @@ export class RolesAdminService {
572
589
  }
573
590
  await this.runEdit(actor, (text) => editAddGroupRef(text, canonical, groupName));
574
591
  });
575
- return this.getRoster();
592
+ return this.getRoster(actor.email);
576
593
  }
577
594
 
578
595
  async unassignGroup(actor: AuthUser, canonical: string, groupName: string): Promise<RoleRosterEntry[]> {
579
596
  await this.runEdit(actor, (text) => editRemoveGroupRef(text, canonical, groupName));
580
- return this.getRoster();
597
+ return this.getRoster(actor.email);
581
598
  }
582
599
 
583
600
  /**
@@ -689,7 +706,7 @@ export class RolesAdminService {
689
706
  );
690
707
  this.accessControl.invalidate(workspaceId);
691
708
  this.emitWrites(workspaceId, actor, [ROLES_YAML, GROUPS_YAML]);
692
- return this.getRoster();
709
+ return this.getRoster(actor.email);
693
710
  }
694
711
 
695
712
  // ---- Internals --------------------------------------------------------