@bevel-software/platform-core-backend 0.11.1 → 0.11.2

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 (312) hide show
  1. package/dist/core/create-core-services.d.ts +1 -1
  2. package/dist/core/create-core-services.d.ts.map +1 -1
  3. package/dist/core/create-core-services.js +2 -2
  4. package/dist/core/create-core-services.js.map +1 -1
  5. package/dist/modules/access/access-control.service.d.ts +1 -253
  6. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  7. package/dist/modules/access/access-control.service.js +3 -632
  8. package/dist/modules/access/access-control.service.js.map +1 -1
  9. package/dist/modules/access/access-declarations.d.ts +2 -2
  10. package/dist/modules/access/access-declarations.d.ts.map +1 -1
  11. package/dist/modules/access/access-declarations.js +2 -2
  12. package/dist/modules/access/access-declarations.js.map +1 -1
  13. package/dist/modules/access/access-mutation.service.d.ts +3 -3
  14. package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
  15. package/dist/modules/access/access-mutation.service.js +3 -3
  16. package/dist/modules/access/access-mutation.service.js.map +1 -1
  17. package/dist/modules/access/access.routes.js +4 -4
  18. package/dist/modules/access/access.routes.js.map +1 -1
  19. package/dist/modules/access/admin-locked-commit.d.ts +2 -2
  20. package/dist/modules/access/admin-locked-commit.d.ts.map +1 -1
  21. package/dist/modules/access/admin-locked-commit.js +2 -2
  22. package/dist/modules/access/admin-locked-commit.js.map +1 -1
  23. package/dist/modules/access/admin-route-helpers.js +1 -1
  24. package/dist/modules/access/admin-route-helpers.js.map +1 -1
  25. package/dist/modules/access/capability-registry.js +1 -1
  26. package/dist/modules/access/capability-registry.js.map +1 -1
  27. package/dist/modules/access/creator-access.d.ts +1 -45
  28. package/dist/modules/access/creator-access.d.ts.map +1 -1
  29. package/dist/modules/access/creator-access.js +4 -24
  30. package/dist/modules/access/creator-access.js.map +1 -1
  31. package/dist/modules/access/groups-admin.routes.js +1 -1
  32. package/dist/modules/access/groups-admin.routes.js.map +1 -1
  33. package/dist/modules/access/groups-admin.service.d.ts +1 -1
  34. package/dist/modules/access/groups-admin.service.d.ts.map +1 -1
  35. package/dist/modules/access/groups-admin.service.js +6 -5
  36. package/dist/modules/access/groups-admin.service.js.map +1 -1
  37. package/dist/modules/access/groups-edit.js +2 -2
  38. package/dist/modules/access/groups-edit.js.map +1 -1
  39. package/dist/modules/access/reference-scan.js +1 -1
  40. package/dist/modules/access/reference-scan.js.map +1 -1
  41. package/dist/modules/access/roles-admin.service.d.ts +1 -1
  42. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  43. package/dist/modules/access/roles-admin.service.js +8 -7
  44. package/dist/modules/access/roles-admin.service.js.map +1 -1
  45. package/dist/modules/access/roles-edit.js +1 -1
  46. package/dist/modules/access/roles-edit.js.map +1 -1
  47. package/dist/modules/access/synced-groups-committer.js +4 -4
  48. package/dist/modules/access/synced-groups-committer.js.map +1 -1
  49. package/dist/modules/access/synced-groups-writer.js +2 -2
  50. package/dist/modules/access/synced-groups-writer.js.map +1 -1
  51. package/dist/modules/access-model/access-errors.d.ts +34 -0
  52. package/dist/modules/access-model/access-errors.d.ts.map +1 -0
  53. package/dist/modules/access-model/access-errors.js +40 -0
  54. package/dist/modules/access-model/access-errors.js.map +1 -0
  55. package/dist/modules/access-model/access-grammar.d.ts +266 -0
  56. package/dist/modules/access-model/access-grammar.d.ts.map +1 -0
  57. package/dist/modules/access-model/access-grammar.js +644 -0
  58. package/dist/modules/access-model/access-grammar.js.map +1 -0
  59. package/dist/modules/access-model/access-splice.d.ts +140 -0
  60. package/dist/modules/access-model/access-splice.d.ts.map +1 -0
  61. package/dist/modules/access-model/access-splice.js +389 -0
  62. package/dist/modules/access-model/access-splice.js.map +1 -0
  63. package/dist/modules/access-model/creator.d.ts +54 -0
  64. package/dist/modules/access-model/creator.d.ts.map +1 -0
  65. package/dist/modules/access-model/creator.js +30 -0
  66. package/dist/modules/access-model/creator.js.map +1 -0
  67. package/dist/modules/access-model/group-files.d.ts +83 -0
  68. package/dist/modules/access-model/group-files.d.ts.map +1 -0
  69. package/dist/modules/access-model/group-files.js +167 -0
  70. package/dist/modules/access-model/group-files.js.map +1 -0
  71. package/dist/modules/access-model/kb-read-filter.d.ts +41 -0
  72. package/dist/modules/access-model/kb-read-filter.d.ts.map +1 -0
  73. package/dist/modules/access-model/kb-read-filter.js +60 -0
  74. package/dist/modules/access-model/kb-read-filter.js.map +1 -0
  75. package/dist/modules/access-model/render-roles-yaml.d.ts +22 -0
  76. package/dist/modules/access-model/render-roles-yaml.d.ts.map +1 -0
  77. package/dist/modules/access-model/render-roles-yaml.js +56 -0
  78. package/dist/modules/access-model/render-roles-yaml.js.map +1 -0
  79. package/dist/modules/access-model/roles-yaml-guard.d.ts +56 -0
  80. package/dist/modules/access-model/roles-yaml-guard.d.ts.map +1 -0
  81. package/dist/modules/access-model/roles-yaml-guard.js +79 -0
  82. package/dist/modules/access-model/roles-yaml-guard.js.map +1 -0
  83. package/dist/modules/admin/admin-access.service.d.ts.map +1 -1
  84. package/dist/modules/admin/admin-access.service.js +1 -1
  85. package/dist/modules/admin/admin-access.service.js.map +1 -1
  86. package/dist/modules/diff/diff.routes.js +4 -4
  87. package/dist/modules/diff/diff.routes.js.map +1 -1
  88. package/dist/modules/diff/diff.service.d.ts +1 -1
  89. package/dist/modules/diff/diff.service.d.ts.map +1 -1
  90. package/dist/modules/kb-fs/branch-name.d.ts +10 -0
  91. package/dist/modules/kb-fs/branch-name.d.ts.map +1 -0
  92. package/dist/modules/kb-fs/branch-name.js +76 -0
  93. package/dist/modules/kb-fs/branch-name.js.map +1 -0
  94. package/dist/modules/kb-fs/clone-config.d.ts +61 -0
  95. package/dist/modules/kb-fs/clone-config.d.ts.map +1 -0
  96. package/dist/modules/kb-fs/clone-config.js +69 -0
  97. package/dist/modules/kb-fs/clone-config.js.map +1 -0
  98. package/dist/modules/kb-fs/file-change-notifier.d.ts +38 -0
  99. package/dist/modules/kb-fs/file-change-notifier.d.ts.map +1 -0
  100. package/dist/modules/kb-fs/file-change-notifier.js +22 -0
  101. package/dist/modules/kb-fs/file-change-notifier.js.map +1 -0
  102. package/dist/modules/kb-fs/locking-filesystem.d.ts +137 -0
  103. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -0
  104. package/dist/modules/kb-fs/locking-filesystem.js +553 -0
  105. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -0
  106. package/dist/modules/kb-fs/mutex.d.ts +11 -0
  107. package/dist/modules/kb-fs/mutex.d.ts.map +1 -0
  108. package/dist/modules/kb-fs/mutex.js +23 -0
  109. package/dist/modules/kb-fs/mutex.js.map +1 -0
  110. package/dist/modules/kb-fs/read-only-filesystem.d.ts +24 -0
  111. package/dist/modules/kb-fs/read-only-filesystem.d.ts.map +1 -0
  112. package/dist/modules/kb-fs/read-only-filesystem.js +39 -0
  113. package/dist/modules/kb-fs/read-only-filesystem.js.map +1 -0
  114. package/dist/modules/plugins/join-proposals.d.ts +1 -1
  115. package/dist/modules/plugins/join-proposals.d.ts.map +1 -1
  116. package/dist/modules/plugins/join-proposals.js +1 -1
  117. package/dist/modules/plugins/join-proposals.js.map +1 -1
  118. package/dist/modules/plugins/join-requests.service.d.ts.map +1 -1
  119. package/dist/modules/plugins/join-requests.service.js +1 -1
  120. package/dist/modules/plugins/join-requests.service.js.map +1 -1
  121. package/dist/modules/plugins/plugin-provision.service.js +3 -3
  122. package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
  123. package/dist/modules/plugins/plugins.routes.js +2 -2
  124. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  125. package/dist/modules/plugins/plugins.service.js +1 -1
  126. package/dist/modules/plugins/plugins.service.js.map +1 -1
  127. package/dist/modules/secrets-vault/secrets-vault.routes.js +1 -1
  128. package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
  129. package/dist/modules/skills/pending-skills.service.d.ts.map +1 -1
  130. package/dist/modules/skills/pending-skills.service.js +1 -1
  131. package/dist/modules/skills/pending-skills.service.js.map +1 -1
  132. package/dist/modules/skills/skills.service.js +1 -1
  133. package/dist/modules/skills/skills.service.js.map +1 -1
  134. package/dist/modules/tool-helpers/tool-context.d.ts +2 -2
  135. package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
  136. package/dist/modules/tool-helpers/tool-context.js +4 -4
  137. package/dist/modules/tool-helpers/tool-context.js.map +1 -1
  138. package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts +1 -1
  139. package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts.map +1 -1
  140. package/dist/modules/tool-manuals/mcp-server-edit.service.js +1 -1
  141. package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -1
  142. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +1 -1
  143. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
  144. package/dist/modules/tool-manuals/tool-manuals.routes.js +1 -1
  145. package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
  146. package/dist/modules/tool-manuals/tool-manuals.service.js +1 -1
  147. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  148. package/dist/modules/tool-manuals/tool-manuals.tools.js +1 -1
  149. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  150. package/dist/modules/workflow/agent-tools/workflow.tools.js +1 -1
  151. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  152. package/dist/modules/workflow/file-lock.service.js +1 -1
  153. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  154. package/dist/modules/workflow/git/git.service.d.ts +2 -2
  155. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  156. package/dist/modules/workflow/git/git.service.js +5 -5
  157. package/dist/modules/workflow/git/git.service.js.map +1 -1
  158. package/dist/modules/workflow/git/pull-request.service.js +1 -1
  159. package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
  160. package/dist/modules/workflow/review-workflow/review-workflow.service.js +2 -2
  161. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
  162. package/dist/modules/workflow/session-ontology.service.js +1 -1
  163. package/dist/modules/workflow/session-ontology.service.js.map +1 -1
  164. package/dist/modules/workflow/workflow.routes.js +2 -2
  165. package/dist/modules/workflow/workflow.routes.js.map +1 -1
  166. package/dist/modules/workflow/workflow.service.d.ts +1 -1
  167. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  168. package/dist/modules/workflow/workflow.service.js +4 -4
  169. package/dist/modules/workflow/workflow.service.js.map +1 -1
  170. package/dist/modules/workspace/startup/kb-startup-runner.js +1 -1
  171. package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
  172. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js +1 -1
  173. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js.map +1 -1
  174. package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts +1 -1
  175. package/dist/modules/workspace/startup/steps/roles-yaml.step.js +2 -2
  176. package/dist/modules/workspace/startup/steps/roles-yaml.step.js.map +1 -1
  177. package/dist/modules/workspace/startup/steps/seed-tree.js +1 -1
  178. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
  179. package/dist/modules/workspace/workspace.routes.d.ts +1 -1
  180. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  181. package/dist/modules/workspace/workspace.routes.js +5 -4
  182. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  183. package/dist/modules/workspace/workspace.service.d.ts +2 -9
  184. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  185. package/dist/modules/workspace/workspace.service.js +14 -15
  186. package/dist/modules/workspace/workspace.service.js.map +1 -1
  187. package/dist/modules/workspace/workspace.tools.js +3 -3
  188. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  189. package/dist/shared/domain-errors.d.ts +202 -0
  190. package/dist/shared/domain-errors.d.ts.map +1 -0
  191. package/dist/shared/domain-errors.js +303 -0
  192. package/dist/shared/domain-errors.js.map +1 -0
  193. package/dist/shared/workspace-id.d.ts +27 -0
  194. package/dist/shared/workspace-id.d.ts.map +1 -0
  195. package/dist/shared/workspace-id.js +36 -0
  196. package/dist/shared/workspace-id.js.map +1 -0
  197. package/package.json +3 -3
  198. package/src/core/create-core-services.ts +2 -2
  199. package/src/modules/access/__tests__/access-control.service.test.ts +1 -1
  200. package/src/modules/access/__tests__/access-groups.test.ts +6 -5
  201. package/src/modules/access/__tests__/access-md-format.test.ts +3 -3
  202. package/src/modules/access/__tests__/access-mutation.service.test.ts +1 -1
  203. package/src/modules/access/__tests__/admin-locked-commit.test.ts +1 -1
  204. package/src/modules/access/__tests__/admin-route-helpers.test.ts +1 -1
  205. package/src/modules/access/__tests__/roles-admin.service.test.ts +2 -2
  206. package/src/modules/access/__tests__/roles-edit.test.ts +1 -1
  207. package/src/modules/access/__tests__/synced-groups-committer.test.ts +1 -1
  208. package/src/modules/access/__tests__/synced-groups-writer.test.ts +1 -1
  209. package/src/modules/access/access-control.service.ts +23 -768
  210. package/src/modules/access/access-declarations.ts +2 -2
  211. package/src/modules/access/access-mutation.service.ts +3 -3
  212. package/src/modules/access/access.routes.ts +5 -5
  213. package/src/modules/access/admin-locked-commit.ts +2 -2
  214. package/src/modules/access/admin-route-helpers.ts +1 -1
  215. package/src/modules/access/capability-registry.ts +1 -1
  216. package/src/modules/access/creator-access.ts +9 -72
  217. package/src/modules/access/groups-admin.routes.ts +1 -1
  218. package/src/modules/access/groups-admin.service.ts +6 -7
  219. package/src/modules/access/groups-edit.ts +2 -2
  220. package/src/modules/access/reference-scan.ts +1 -1
  221. package/src/modules/access/roles-admin.service.ts +8 -8
  222. package/src/modules/access/roles-edit.ts +1 -1
  223. package/src/modules/access/synced-groups-committer.ts +4 -4
  224. package/src/modules/access/synced-groups-writer.ts +2 -2
  225. package/src/modules/access-model/__tests__/access-grammar.test.ts +31 -0
  226. package/src/modules/{access → access-model}/__tests__/access-splice.test.ts +268 -268
  227. package/src/modules/{access → access-model}/access-errors.ts +1 -1
  228. package/src/modules/access-model/access-grammar.ts +778 -0
  229. package/src/modules/{access → access-model}/access-splice.ts +1 -1
  230. package/src/modules/access-model/creator.ts +79 -0
  231. package/src/modules/{access → access-model}/group-files.ts +1 -1
  232. package/src/modules/{access → access-model}/render-roles-yaml.ts +1 -1
  233. package/src/modules/{access → access-model}/roles-yaml-guard.ts +2 -2
  234. package/src/modules/admin/admin-access.service.ts +2 -1
  235. package/src/modules/diff/__tests__/diff.routes.rejectPathsLocked.test.ts +2 -2
  236. package/src/modules/diff/__tests__/diff.service.seed-atomicity.test.ts +3 -2
  237. package/src/modules/diff/__tests__/diff.service.test.ts +3 -2
  238. package/src/modules/diff/diff.routes.ts +4 -4
  239. package/src/modules/diff/diff.service.ts +1 -1
  240. package/src/modules/{workflow/git → kb-fs}/__tests__/branch-name.test.ts +1 -1
  241. package/src/modules/{workflow → kb-fs}/__tests__/locking-filesystem.test.ts +1 -1
  242. package/src/modules/{workflow/git → kb-fs}/branch-name.ts +1 -1
  243. package/src/modules/{workflow → kb-fs}/locking-filesystem.ts +2 -2
  244. package/src/modules/plugins/__tests__/join-requests.service.test.ts +2 -4
  245. package/src/modules/plugins/__tests__/plugin-index.service.test.ts +1 -1
  246. package/src/modules/plugins/__tests__/plugins.routes.test.ts +1 -1
  247. package/src/modules/plugins/join-proposals.ts +1 -1
  248. package/src/modules/plugins/join-requests.service.ts +2 -1
  249. package/src/modules/plugins/plugin-provision.service.ts +3 -3
  250. package/src/modules/plugins/plugins.routes.ts +2 -2
  251. package/src/modules/plugins/plugins.service.ts +1 -1
  252. package/src/modules/secrets-vault/secrets-vault.routes.ts +1 -1
  253. package/src/modules/skills/__tests__/skills.service.test.ts +1 -1
  254. package/src/modules/skills/pending-skills.service.ts +2 -1
  255. package/src/modules/skills/skills.service.ts +1 -1
  256. package/src/modules/tool-helpers/tool-context.ts +6 -5
  257. package/src/modules/tool-manuals/__tests__/mcp-server-edit.service.test.ts +1 -1
  258. package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +1 -1
  259. package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +1 -1
  260. package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +1 -1
  261. package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +1 -1
  262. package/src/modules/tool-manuals/mcp-server-edit.service.ts +2 -1
  263. package/src/modules/tool-manuals/tool-manuals.routes.ts +2 -1
  264. package/src/modules/tool-manuals/tool-manuals.service.ts +1 -1
  265. package/src/modules/tool-manuals/tool-manuals.tools.ts +1 -1
  266. package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +1 -1
  267. package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +1 -1
  268. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +1 -1
  269. package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +1 -1
  270. package/src/modules/workflow/agent-tools/workflow.tools.ts +1 -1
  271. package/src/modules/workflow/file-lock.service.ts +1 -1
  272. package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +1 -1
  273. package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +1 -1
  274. package/src/modules/workflow/git/__tests__/git.service.diffFileAtCommit.test.ts +1 -1
  275. package/src/modules/workflow/git/__tests__/git.service.diffFileBetweenBranches.test.ts +1 -1
  276. package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +1 -1
  277. package/src/modules/workflow/git/git.service.ts +5 -5
  278. package/src/modules/workflow/git/pull-request.service.ts +1 -1
  279. package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +1 -1
  280. package/src/modules/workflow/review-workflow/review-workflow.service.ts +2 -2
  281. package/src/modules/workflow/session-ontology.service.ts +1 -1
  282. package/src/modules/workflow/workflow.routes.ts +2 -2
  283. package/src/modules/workflow/workflow.service.ts +5 -5
  284. package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +1 -1
  285. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +1 -1
  286. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +2 -2
  287. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +1 -1
  288. package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +2 -5
  289. package/src/modules/workspace/__tests__/workspace.service.test.ts +13 -1
  290. package/src/modules/workspace/__tests__/workspace.tools.test.ts +1 -1
  291. package/src/modules/workspace/startup/kb-startup-runner.ts +1 -1
  292. package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +1 -1
  293. package/src/modules/workspace/startup/steps/groups-to-plugins.step.ts +1 -1
  294. package/src/modules/workspace/startup/steps/roles-yaml.step.ts +2 -2
  295. package/src/modules/workspace/startup/steps/seed-tree.ts +1 -1
  296. package/src/modules/workspace/workspace.routes.ts +6 -5
  297. package/src/modules/workspace/workspace.service.ts +14 -18
  298. package/src/modules/workspace/workspace.tools.ts +3 -3
  299. package/src/shared/__tests__/join-request.test.ts +1 -1
  300. package/src/shared/__tests__/workspace-id.test.ts +17 -0
  301. package/src/{modules/workflow/workflow.errors.ts → shared/domain-errors.ts} +6 -1
  302. package/src/shared/workspace-id.ts +36 -0
  303. /package/src/modules/{access → access-model}/__tests__/kb-read-filter.test.ts +0 -0
  304. /package/src/modules/{access → access-model}/__tests__/roles-yaml-guard.test.ts +0 -0
  305. /package/src/modules/{access → access-model}/kb-read-filter.ts +0 -0
  306. /package/src/modules/{workflow/git → kb-fs}/__tests__/clone-config.test.ts +0 -0
  307. /package/src/modules/{workflow → kb-fs}/__tests__/file-change-notifier.test.ts +0 -0
  308. /package/src/modules/{workflow/git → kb-fs}/__tests__/mutex.test.ts +0 -0
  309. /package/src/modules/{workflow/git → kb-fs}/clone-config.ts +0 -0
  310. /package/src/modules/{workflow → kb-fs}/file-change-notifier.ts +0 -0
  311. /package/src/modules/{workflow/git → kb-fs}/mutex.ts +0 -0
  312. /package/src/modules/{workflow → kb-fs}/read-only-filesystem.ts +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"creator.d.ts","sourceRoot":"","sources":["../../../src/modules/access-model/creator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAA;CAAE,GACjF;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAA;CAAE,CAAC;AAEhE,mDAAmD;AACnD,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,aAAa,CACX,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GAAG,KAAK,GACnB,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IAErC,oBAAoB,CAClB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAE1B,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAClD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,CAE5D"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The pure half of the creator read-grant story: the creator identity, the
3
+ * grant-plan contract the creation surfaces consume, and the splice-safe
4
+ * principal for a creator. The service that DECIDES when a creation needs a
5
+ * grant (filesystem stats, access checks) is `modules/access/creator-access.ts`;
6
+ * this file carries only the types and pure helpers so downstream modules
7
+ * (kb-fs, plugins, tool-helpers) never need an edge into the access module.
8
+ */
9
+ /**
10
+ * The splice-safe principal for a creator — exported because plugin
11
+ * provisioning (`PluginProvisionService`) writes the same `Name <email>`
12
+ * entries into the access.md files it seeds, and two spellings of the same
13
+ * person would read as two people.
14
+ */
15
+ export function creatorPrincipal(creator) {
16
+ return { kind: 'user', email: creator.email, displayName: safeDisplayName(creator) };
17
+ }
18
+ /**
19
+ * `Name <email>`-safe display name: `validatePrincipal` rejects control
20
+ * chars, `<`, `>`, and `#`, so strip those from the user's name and fall
21
+ * back to the email local part (which the email regex already keeps free of
22
+ * `<>`/whitespace) when nothing usable remains.
23
+ */
24
+ function safeDisplayName(creator) {
25
+ const strip = (s) =>
26
+ // eslint-disable-next-line no-control-regex -- same intentional control-char guard as access-splice
27
+ s.replace(/[<>#\u0000-\u001f\u007f]/g, ' ').replace(/\s+/g, ' ').trim();
28
+ return strip(creator.name) || strip(creator.email.split('@')[0] ?? '') || 'KB user';
29
+ }
30
+ //# sourceMappingURL=creator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"creator.js","sourceRoot":"","sources":["../../../src/modules/access-model/creator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAkDH;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAgB;IAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,WAAW,EAAE,eAAe,CAAC,OAAO,CAAC,EAAE,CAAC;AACvF,CAAC;AAED;;;;;GAKG;AACH,SAAS,eAAe,CAAC,OAAgB;IACvC,MAAM,KAAK,GAAG,CAAC,CAAS,EAAE,EAAE;IAC1B,oGAAoG;IACpG,CAAC,CAAC,OAAO,CAAC,2BAA2B,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1E,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,SAAS,CAAC;AACtF,CAAC"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Group files — the "who you are" half of the roles/groups split.
3
+ *
4
+ * Groups are people-sets referenced by access grants ("GTM Team can read the
5
+ * battlecards"). A deployment has exactly ONE group source at a time — a mode
6
+ * derived from which file exists, so it holds at any git ref:
7
+ *
8
+ * - `synced-groups.yaml` EXISTS → IdP mode. The file is MACHINE-OWNED,
9
+ * regenerated wholesale from the SCIM directory mirror; membership is
10
+ * managed in the IdP. `groups.yaml` is ignored entirely.
11
+ * - otherwise → manual mode. `groups.yaml` (UI-edited)
12
+ * is the source.
13
+ *
14
+ * Both files share one format, the group twin of roles.yaml:
15
+ *
16
+ * groups:
17
+ * Engineering:
18
+ * - ada@x.io
19
+ * - bo@x.io
20
+ * Empty Group: []
21
+ *
22
+ * Parsing is FORGIVING at the entry level by design — a malformed email or a
23
+ * reserved/duplicate name skips that entry with a warning, it never takes the
24
+ * whole file down. Only structural failures (unparsable YAML, `groups:` not a
25
+ * mapping) reject the file, and even then the RESOLVER degrades to "this file
26
+ * contributes nothing" rather than throwing: unlike roles.yaml there is no
27
+ * admin-lockout risk here, so groups must never be able to brick access
28
+ * resolution. Strict acceptance for WRITES is the job of the write-side
29
+ * validator (`validateGroupsFile` below), which treats warnings as refusals.
30
+ */
31
+ export declare const GROUPS_YAML = "groups.yaml";
32
+ export declare const SYNCED_GROUPS_YAML = "synced-groups.yaml";
33
+ /**
34
+ * THE name-safety predicate for principal display names — the single source
35
+ * of truth every surface derives from (group creation/rename, IdP sync
36
+ * materialization; thin assert wrappers sit on top). Returns a human-readable
37
+ * reason the name is unsafe, or null when it is fine.
38
+ *
39
+ * The character rules keep the emitted YAML parseable and the entry grammar
40
+ * unambiguous:
41
+ * `:` mis-tokenises as a nested mapping key
42
+ * `#` truncated as a comment by the subset parser's stripComment
43
+ * `<` / `>` collide with the `Name <email>` user-reference shape
44
+ * leading - tokenises as a list item
45
+ * \x00-\x1f control chars / newlines break line structure outright
46
+ * `role/…` reserved: that spelling is the EXPLICIT role token in access
47
+ * entries — a group carrying it could never be referenced.
48
+ */
49
+ export declare function unsafeNameReason(displayName: string): string | null;
50
+ /** One parsed group: display name + canonicalised member emails. */
51
+ export interface GroupDefinition {
52
+ displayName: string;
53
+ emails: Set<string>;
54
+ }
55
+ /** Canonical group name → definition, in file order. */
56
+ export type GroupsIndex = Map<string, GroupDefinition>;
57
+ export interface ParsedGroupsFile {
58
+ ok: true;
59
+ groups: GroupsIndex;
60
+ /** Entry-level problems that were skipped over (bad email, dup name, …). */
61
+ warnings: string[];
62
+ }
63
+ export interface GroupsFileError {
64
+ ok: false;
65
+ errors: string[];
66
+ }
67
+ /**
68
+ * Parse a group file (either of the two — same grammar). See the module note
69
+ * for the forgiving-vs-structural split.
70
+ */
71
+ export declare function parseGroupsFile(text: string, filename: string): ParsedGroupsFile | GroupsFileError;
72
+ /**
73
+ * Write-gate twin of {@link parseGroupsFile}: strict — structural errors AND
74
+ * entry-level warnings both refuse the candidate, so a UI write can never land
75
+ * a group the resolver would silently skip.
76
+ */
77
+ export declare function validateGroupsFile(text: string, filename: string): {
78
+ ok: true;
79
+ } | {
80
+ ok: false;
81
+ errors: string[];
82
+ };
83
+ //# sourceMappingURL=group-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group-files.d.ts","sourceRoot":"","sources":["../../../src/modules/access-model/group-files.ts"],"names":[],"mappings":"AAUA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,eAAO,MAAM,WAAW,gBAAgB,CAAC;AACzC,eAAO,MAAM,kBAAkB,uBAAuB,CAAC;AAEvD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAWnE;AAED,oEAAoE;AACpE,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACrB;AAED,wDAAwD;AACxD,MAAM,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;AAEvD,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,IAAI,CAAC;IACT,MAAM,EAAE,WAAW,CAAC;IACpB,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,gBAAgB,GAAG,eAAe,CAyFpC;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAWhD"}
@@ -0,0 +1,167 @@
1
+ import { EMAIL_REGEX, GROUP_REF_PREFIX, RESERVED_ROLE_NAMES, ROLE_TOKEN_PREFIX, canonicalEmail, canonicalRoleName, parseYamlSubset, } from './access-grammar.js';
2
+ /**
3
+ * Group files — the "who you are" half of the roles/groups split.
4
+ *
5
+ * Groups are people-sets referenced by access grants ("GTM Team can read the
6
+ * battlecards"). A deployment has exactly ONE group source at a time — a mode
7
+ * derived from which file exists, so it holds at any git ref:
8
+ *
9
+ * - `synced-groups.yaml` EXISTS → IdP mode. The file is MACHINE-OWNED,
10
+ * regenerated wholesale from the SCIM directory mirror; membership is
11
+ * managed in the IdP. `groups.yaml` is ignored entirely.
12
+ * - otherwise → manual mode. `groups.yaml` (UI-edited)
13
+ * is the source.
14
+ *
15
+ * Both files share one format, the group twin of roles.yaml:
16
+ *
17
+ * groups:
18
+ * Engineering:
19
+ * - ada@x.io
20
+ * - bo@x.io
21
+ * Empty Group: []
22
+ *
23
+ * Parsing is FORGIVING at the entry level by design — a malformed email or a
24
+ * reserved/duplicate name skips that entry with a warning, it never takes the
25
+ * whole file down. Only structural failures (unparsable YAML, `groups:` not a
26
+ * mapping) reject the file, and even then the RESOLVER degrades to "this file
27
+ * contributes nothing" rather than throwing: unlike roles.yaml there is no
28
+ * admin-lockout risk here, so groups must never be able to brick access
29
+ * resolution. Strict acceptance for WRITES is the job of the write-side
30
+ * validator (`validateGroupsFile` below), which treats warnings as refusals.
31
+ */
32
+ export const GROUPS_YAML = 'groups.yaml';
33
+ export const SYNCED_GROUPS_YAML = 'synced-groups.yaml';
34
+ /**
35
+ * THE name-safety predicate for principal display names — the single source
36
+ * of truth every surface derives from (group creation/rename, IdP sync
37
+ * materialization; thin assert wrappers sit on top). Returns a human-readable
38
+ * reason the name is unsafe, or null when it is fine.
39
+ *
40
+ * The character rules keep the emitted YAML parseable and the entry grammar
41
+ * unambiguous:
42
+ * `:` mis-tokenises as a nested mapping key
43
+ * `#` truncated as a comment by the subset parser's stripComment
44
+ * `<` / `>` collide with the `Name <email>` user-reference shape
45
+ * leading - tokenises as a list item
46
+ * \x00-\x1f control chars / newlines break line structure outright
47
+ * `role/…` reserved: that spelling is the EXPLICIT role token in access
48
+ * entries — a group carrying it could never be referenced.
49
+ */
50
+ export function unsafeNameReason(displayName) {
51
+ const trimmed = displayName.trim();
52
+ if (!trimmed)
53
+ return 'empty name';
54
+ if (/[:#<>]/.test(trimmed))
55
+ return "contains ':', '#', '<', or '>'";
56
+ // eslint-disable-next-line no-control-regex
57
+ if (/[\x00-\x1f]/.test(trimmed))
58
+ return 'contains control characters';
59
+ if (trimmed.startsWith('-'))
60
+ return "starts with '-'";
61
+ if (canonicalRoleName(trimmed).startsWith(ROLE_TOKEN_PREFIX)) {
62
+ return `starts with the reserved '${ROLE_TOKEN_PREFIX}' prefix (the explicit role token in access entries)`;
63
+ }
64
+ return null;
65
+ }
66
+ /**
67
+ * Parse a group file (either of the two — same grammar). See the module note
68
+ * for the forgiving-vs-structural split.
69
+ */
70
+ export function parseGroupsFile(text, filename) {
71
+ // Tolerate empty keys at parse: this file's contract is ENTRY-level
72
+ // forgiveness (a blank group name is skipped with a warning below), and a
73
+ // hard tokenizer error here would retire every OTHER group fail-closed.
74
+ const parsed = parseYamlSubset(text, { tolerateEmptyKeys: true });
75
+ if (!parsed.ok)
76
+ return { ok: false, errors: [`${filename}: ${parsed.error}`] };
77
+ const root = parsed.value;
78
+ if (root == null || typeof root !== 'object' || Array.isArray(root)) {
79
+ return { ok: false, errors: [`${filename}: must be a top-level mapping`] };
80
+ }
81
+ const groupsNode = root.groups;
82
+ // An empty file (or one with no `groups:` key yet) is a valid empty set —
83
+ // a fresh deployment's groups.yaml starts this way.
84
+ if (groupsNode == null)
85
+ return { ok: true, groups: new Map(), warnings: [] };
86
+ if (typeof groupsNode !== 'object' || Array.isArray(groupsNode)) {
87
+ return { ok: false, errors: [`${filename}: 'groups' must be a mapping`] };
88
+ }
89
+ const groups = new Map();
90
+ const warnings = [];
91
+ for (const [displayName, value] of Object.entries(groupsNode)) {
92
+ const canonical = canonicalRoleName(displayName);
93
+ if (!canonical) {
94
+ warnings.push(`${filename}: empty group name — skipped`);
95
+ continue;
96
+ }
97
+ // THE shared name-safety predicate (see `unsafeNameReason` above) — the
98
+ // same rules every write surface asserts. The parser must apply it too:
99
+ // a name the writers refuse (control chars, `<`/`>`, a leading `-`, the
100
+ // reserved `role/` prefix) can still reach a group file by hand edit or
101
+ // through a skewed writer, and accepting it here would let the resolver
102
+ // honor — and `validateGroupsFile` pass — a name no editor can ever
103
+ // produce or reference safely.
104
+ const unsafe = unsafeNameReason(displayName);
105
+ if (unsafe) {
106
+ warnings.push(`${filename}: group ${JSON.stringify(displayName)} ${unsafe} — skipped`);
107
+ continue;
108
+ }
109
+ if (RESERVED_ROLE_NAMES.has(canonical)) {
110
+ warnings.push(`${filename}: group '${displayName}' uses reserved name '${canonical}' — skipped`);
111
+ continue;
112
+ }
113
+ if (groups.has(canonical)) {
114
+ warnings.push(`${filename}: group '${displayName}' canonicalises to '${canonical}', already declared as '${groups.get(canonical).displayName}' — skipped`);
115
+ continue;
116
+ }
117
+ if (value !== null && !Array.isArray(value)) {
118
+ warnings.push(`${filename}: group '${displayName}' must be a list of emails — skipped`);
119
+ continue;
120
+ }
121
+ const emails = new Set();
122
+ for (const raw of value ?? []) {
123
+ if (typeof raw !== 'string') {
124
+ warnings.push(`${filename}: group '${displayName}' has a non-string entry — entry skipped`);
125
+ continue;
126
+ }
127
+ const email = canonicalEmail(raw);
128
+ // The reserved `group:` prefix would PASS the email regex
129
+ // (`group:lee@x.io` shapes like an email) but it is the roles.yaml
130
+ // group-reference token, not an address — groups contain emails, never
131
+ // other groups. Skip it with its own warning so the write gate refuses
132
+ // it and the resolver never grants a colon-bearing "email".
133
+ if (email.startsWith(GROUP_REF_PREFIX)) {
134
+ warnings.push(`${filename}: group '${displayName}' has a '${GROUP_REF_PREFIX}'-prefixed entry ${JSON.stringify(raw)} — groups contain emails, not group references; entry skipped`);
135
+ continue;
136
+ }
137
+ if (!EMAIL_REGEX.test(email)) {
138
+ warnings.push(`${filename}: group '${displayName}' has malformed email '${raw}' — entry skipped`);
139
+ continue;
140
+ }
141
+ emails.add(email);
142
+ }
143
+ groups.set(canonical, { displayName: displayName.trim(), emails });
144
+ }
145
+ return { ok: true, groups, warnings };
146
+ }
147
+ /**
148
+ * Write-gate twin of {@link parseGroupsFile}: strict — structural errors AND
149
+ * entry-level warnings both refuse the candidate, so a UI write can never land
150
+ * a group the resolver would silently skip.
151
+ */
152
+ export function validateGroupsFile(text, filename) {
153
+ // Strict PARSE first (no empty-key tolerance): the read side forgives a
154
+ // blank key entry-level so other groups keep resolving, but a WRITE that
155
+ // contains one — anywhere, including outside the `groups:` mapping — must
156
+ // refuse rather than land bytes the tolerant reader silently drops.
157
+ const strict = parseYamlSubset(text.trim() ? text : 'groups:');
158
+ if (!strict.ok)
159
+ return { ok: false, errors: [`${filename}: ${strict.error}`] };
160
+ const parsed = parseGroupsFile(text, filename);
161
+ if (!parsed.ok)
162
+ return { ok: false, errors: parsed.errors };
163
+ if (parsed.warnings.length > 0)
164
+ return { ok: false, errors: parsed.warnings };
165
+ return { ok: true };
166
+ }
167
+ //# sourceMappingURL=group-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group-files.js","sourceRoot":"","sources":["../../../src/modules/access-model/group-files.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Shared read-filter primitive.
3
+ *
4
+ * One place that knows how to turn workspace-relative paths into read verdicts,
5
+ * reused by every surface that must hide nodes a user can't read: the agent
6
+ * tools (`read_file`/`list_files`/`grep`), the file-explorer tree, the content
7
+ * routes, the diff/review routes, and the KB graph.
8
+ *
9
+ * It deliberately shares only the PRIMITIVE — the path mapping and the batched
10
+ * verdict map — NOT the inclusion policy. Callers differ on that: the agent's
11
+ * `list_files` keeps directory entries visible (a restricted child is filtered
12
+ * when read), whereas the explorer tree hides a restricted directory's whole
13
+ * subtree. Each caller applies the verdict to its own policy.
14
+ *
15
+ * It is parameterized by the access method to call (`ReadBatchFn`); every
16
+ * caller — the tree, the agent tools, and the graph — now uses the full
17
+ * `canReadBatch` (folder chain + per-node frontmatter `read:` rules), with the
18
+ * per-file frontmatter reads memoized inside the access service.
19
+ */
20
+ /**
21
+ * A batched read check over KB-repo-relative paths. Returns a map keyed by the
22
+ * input paths. `IAccessControl.canReadBatch` satisfies this shape.
23
+ */
24
+ export type ReadBatchFn = (workspaceId: string, userEmail: string, kbRelPaths: string[]) => Promise<Map<string, boolean>>;
25
+ /**
26
+ * Map a workspace-relative path to its KB-repo-relative form (what the access
27
+ * tree is keyed on), or null when the path is outside the KB repo — reserved
28
+ * workspace files and the KB dir itself carry no `read:` rules and are never
29
+ * gated. Tolerates a leading `./` or `/` and a trailing `/`.
30
+ */
31
+ export declare function toKbRelative(p: string, kbDirName: string): string | null;
32
+ /**
33
+ * Resolve read verdicts for a set of workspace-relative paths in ONE batched
34
+ * access call. Non-KB paths (outside `kbDirName`) are always readable — they
35
+ * carry no `read:` rules. KB paths are checked via `batchFn`. Returns a map
36
+ * keyed by the original `wsPaths` (so callers can look up by the entry path
37
+ * they passed). A path absent from the access verdict resolves to `false`
38
+ * (fail-closed) — never silently readable.
39
+ */
40
+ export declare function resolveReadableMap(batchFn: ReadBatchFn, workspaceId: string, userEmail: string, kbDirName: string, wsPaths: string[]): Promise<Map<string, boolean>>;
41
+ //# sourceMappingURL=kb-read-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kb-read-filter.d.ts","sourceRoot":"","sources":["../../../src/modules/access-model/kb-read-filter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,CACxB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAAE,KACjB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEnC;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMxE;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,WAAW,EACpB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAiB/B"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Shared read-filter primitive.
3
+ *
4
+ * One place that knows how to turn workspace-relative paths into read verdicts,
5
+ * reused by every surface that must hide nodes a user can't read: the agent
6
+ * tools (`read_file`/`list_files`/`grep`), the file-explorer tree, the content
7
+ * routes, the diff/review routes, and the KB graph.
8
+ *
9
+ * It deliberately shares only the PRIMITIVE — the path mapping and the batched
10
+ * verdict map — NOT the inclusion policy. Callers differ on that: the agent's
11
+ * `list_files` keeps directory entries visible (a restricted child is filtered
12
+ * when read), whereas the explorer tree hides a restricted directory's whole
13
+ * subtree. Each caller applies the verdict to its own policy.
14
+ *
15
+ * It is parameterized by the access method to call (`ReadBatchFn`); every
16
+ * caller — the tree, the agent tools, and the graph — now uses the full
17
+ * `canReadBatch` (folder chain + per-node frontmatter `read:` rules), with the
18
+ * per-file frontmatter reads memoized inside the access service.
19
+ */
20
+ /**
21
+ * Map a workspace-relative path to its KB-repo-relative form (what the access
22
+ * tree is keyed on), or null when the path is outside the KB repo — reserved
23
+ * workspace files and the KB dir itself carry no `read:` rules and are never
24
+ * gated. Tolerates a leading `./` or `/` and a trailing `/`.
25
+ */
26
+ export function toKbRelative(p, kbDirName) {
27
+ const norm = p.replace(/^\.?\/+/, '').replace(/\/+$/, '');
28
+ const prefix = `${kbDirName}/`;
29
+ if (!norm.startsWith(prefix))
30
+ return null;
31
+ const rel = norm.slice(prefix.length);
32
+ return rel.length > 0 ? rel : null;
33
+ }
34
+ /**
35
+ * Resolve read verdicts for a set of workspace-relative paths in ONE batched
36
+ * access call. Non-KB paths (outside `kbDirName`) are always readable — they
37
+ * carry no `read:` rules. KB paths are checked via `batchFn`. Returns a map
38
+ * keyed by the original `wsPaths` (so callers can look up by the entry path
39
+ * they passed). A path absent from the access verdict resolves to `false`
40
+ * (fail-closed) — never silently readable.
41
+ */
42
+ export async function resolveReadableMap(batchFn, workspaceId, userEmail, kbDirName, wsPaths) {
43
+ const relByWsPath = new Map();
44
+ for (const wp of wsPaths) {
45
+ const rel = toKbRelative(wp, kbDirName);
46
+ if (rel !== null)
47
+ relByWsPath.set(wp, rel);
48
+ }
49
+ const verdict = relByWsPath.size > 0
50
+ ? await batchFn(workspaceId, userEmail, [...new Set(relByWsPath.values())])
51
+ : new Map();
52
+ const out = new Map();
53
+ for (const wp of wsPaths) {
54
+ const rel = relByWsPath.get(wp);
55
+ // Non-KB path → always readable. KB path → the verdict, fail-closed if absent.
56
+ out.set(wp, rel === undefined ? true : verdict.get(rel) === true);
57
+ }
58
+ return out;
59
+ }
60
+ //# sourceMappingURL=kb-read-filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kb-read-filter.js","sourceRoot":"","sources":["../../../src/modules/access-model/kb-read-filter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAYH;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,CAAS,EAAE,SAAiB;IACvD,MAAM,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,GAAG,SAAS,GAAG,CAAC;IAC/B,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACtC,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AACrC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,OAAoB,EACpB,WAAmB,EACnB,SAAiB,EACjB,SAAiB,EACjB,OAAiB;IAEjB,MAAM,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC9C,KAAK,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,YAAY,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;QACxC,IAAI,GAAG,KAAK,IAAI;YAAE,WAAW,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IAC7C,CAAC;IACD,MAAM,OAAO,GACX,WAAW,CAAC,IAAI,GAAG,CAAC;QAClB,CAAC,CAAC,MAAM,OAAO,CAAC,WAAW,EAAE,SAAS,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAC3E,CAAC,CAAC,IAAI,GAAG,EAAmB,CAAC;IACjC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAmB,CAAC;IACvC,KAAK,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChC,+EAA+E;QAC/E,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Render a `roles.yaml` granting Admin to each email — the ONE renderer every
3
+ * generated roles.yaml goes through (empty-remote seeding, the startup
4
+ * top-up, and the break-glass recovery), so none of them can drift on
5
+ * validation and each fails with the same actionable message.
6
+ *
7
+ * Each email is validated first, because a malformed one renders a file with
8
+ * no working Admin — SILENTLY: a leading `#` turns the entry into a YAML
9
+ * comment, an embedded newline breaks the list, an all-whitespace value
10
+ * renders an empty entry. A bad ADMIN_EMAIL is operator error, and the
11
+ * callers' fail-closed contracts want a message naming the fix instead of an
12
+ * un-administrable knowledge base (or a write-validator rejection deep in the
13
+ * recovery path).
14
+ */
15
+ export declare function renderRolesYaml(adminEmails: readonly string[],
16
+ /**
17
+ * Provenance comment appended to the header. The seed/top-up default names
18
+ * where the file came from; recovery passes `null` — claiming "generated at
19
+ * KB-seed time" on a recovery-restored file would mislead whoever reads it.
20
+ */
21
+ provenance?: string | null): string;
22
+ //# sourceMappingURL=render-roles-yaml.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-roles-yaml.d.ts","sourceRoot":"","sources":["../../../src/modules/access-model/render-roles-yaml.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,SAAS,MAAM,EAAE;AAC9B;;;;GAIG;AACH,UAAU,GAAE,MAAM,GAAG,IAA0E,GAC9F,MAAM,CAwCR"}
@@ -0,0 +1,56 @@
1
+ import { parseRolesYaml } from './access-grammar.js';
2
+ /**
3
+ * Render a `roles.yaml` granting Admin to each email — the ONE renderer every
4
+ * generated roles.yaml goes through (empty-remote seeding, the startup
5
+ * top-up, and the break-glass recovery), so none of them can drift on
6
+ * validation and each fails with the same actionable message.
7
+ *
8
+ * Each email is validated first, because a malformed one renders a file with
9
+ * no working Admin — SILENTLY: a leading `#` turns the entry into a YAML
10
+ * comment, an embedded newline breaks the list, an all-whitespace value
11
+ * renders an empty entry. A bad ADMIN_EMAIL is operator error, and the
12
+ * callers' fail-closed contracts want a message naming the fix instead of an
13
+ * un-administrable knowledge base (or a write-validator rejection deep in the
14
+ * recovery path).
15
+ */
16
+ export function renderRolesYaml(adminEmails,
17
+ /**
18
+ * Provenance comment appended to the header. The seed/top-up default names
19
+ * where the file came from; recovery passes `null` — claiming "generated at
20
+ * KB-seed time" on a recovery-restored file would mislead whoever reads it.
21
+ */
22
+ provenance = 'Generated by the Bevel platform at KB-seed time from ADMIN_EMAIL.') {
23
+ const emails = adminEmails.map((raw) => {
24
+ const email = raw.trim();
25
+ if (email === '') {
26
+ throw new Error('Seed admin email is empty — set ADMIN_EMAIL to a real address.');
27
+ }
28
+ if (email.startsWith('#')) {
29
+ throw new Error(`Seed admin email ${JSON.stringify(raw)} starts with "#", which YAML reads as a comment — fix ADMIN_EMAIL.`);
30
+ }
31
+ if (/\s/.test(email)) {
32
+ throw new Error(`Seed admin email ${JSON.stringify(raw)} contains whitespace, which would corrupt roles.yaml — fix ADMIN_EMAIL.`);
33
+ }
34
+ return email;
35
+ });
36
+ const lines = emails.map((e) => ` - ${e}`).join('\n');
37
+ const text = '# Identity → role mapping for access control.\n' +
38
+ '# Role names are case- and whitespace-insensitive. The `Admin` role is special:\n' +
39
+ '# only Admins may edit this file, and at least one Admin must always exist.\n' +
40
+ (provenance !== null ? `#\n# ${provenance}\n` : '') +
41
+ 'roles:\n' +
42
+ ' Admin:\n' +
43
+ `${lines}\n`;
44
+ // Validate by PARSING with the real access parser, not by re-implementing
45
+ // its email grammar: whatever the parser would reject (a `<Name <email>`
46
+ // shape, a non-email string, tomorrow's new rule) must fail here, before an
47
+ // un-administrable roles.yaml gets committed. The checks above exist only
48
+ // to give the common mistakes sharper messages.
49
+ const parsed = parseRolesYaml(text);
50
+ if (!parsed.ok) {
51
+ throw new Error(`Generated roles.yaml from ADMIN_EMAIL would not parse — fix the configured address(es). ` +
52
+ parsed.errors.join('; '));
53
+ }
54
+ return text;
55
+ }
56
+ //# sourceMappingURL=render-roles-yaml.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-roles-yaml.js","sourceRoot":"","sources":["../../../src/modules/access-model/render-roles-yaml.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,eAAe,CAC7B,WAA8B;AAC9B;;;;GAIG;AACH,aAA4B,mEAAmE;IAE/F,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACzB,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAC;QACpF,CAAC;QACD,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CACb,oBAAoB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,oEAAoE,CAC5G,CAAC;QACJ,CAAC;QACD,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CACb,oBAAoB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,yEAAyE,CACjH,CAAC;QACJ,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzD,MAAM,IAAI,GACR,iDAAiD;QACjD,mFAAmF;QACnF,+EAA+E;QAC/E,CAAC,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,UAAU,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QACnD,UAAU;QACV,YAAY;QACZ,GAAG,KAAK,IAAI,CAAC;IACf,0EAA0E;IAC1E,yEAAyE;IACzE,4EAA4E;IAC5E,0EAA0E;IAC1E,gDAAgD;IAChD,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,0FAA0F;YACxF,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAC3B,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Pre-disk validity gate for `roles.yaml`.
3
+ *
4
+ * WHY this exists: `roles.yaml` is the single point of failure for the whole
5
+ * app. The runtime resolver's `loadModel` HARD-THROWS `AccessConfigError` on a
6
+ * parse failure, and `isAdmin` swallows that into `false` for EVERYONE — so a
7
+ * single malformed `roles.yaml` is an app-wide, in-app-unrecoverable admin
8
+ * lockout. The dedicated Roles & Members service already validates every
9
+ * candidate before writing (`assertLoadable`), but the two RAW write paths do
10
+ * not:
11
+ * - the human editor save (`PUT /workspace/:id/file`)
12
+ * - the agent's file tools (`LockingFilesystem.writeFile` / `writeFiles`)
13
+ *
14
+ * Both of those let an admin (or an admin-driven agent) commit a broken file.
15
+ * This guard plugs them: it runs the resolver's OWN parser on the candidate
16
+ * BEFORE a byte hits disk, and refuses the write with a 422 on any error. A
17
+ * write that passes here is loadable by `loadModel`, so the lockout becomes
18
+ * structurally unreachable through normal edits.
19
+ *
20
+ * The break-glass recovery path (for a file that got broken some OTHER way —
21
+ * a direct git push, a pre-guard commit) lives in `roles-admin.service.ts`.
22
+ */
23
+ import type { FileContent } from '@mastra/core/workspace';
24
+ import { WorkflowDomainError } from '../../shared/domain-errors.js';
25
+ /** Repo-relative basename of the roles file (it lives at the KB repo root). */
26
+ export declare const ROLES_YAML_BASENAME = "roles.yaml";
27
+ /**
28
+ * A raw write whose candidate `roles.yaml` would not parse. 422 (bad input) —
29
+ * the edit is refused and nothing is written. Carries the parser errors so the
30
+ * editor / agent can show exactly what's wrong.
31
+ */
32
+ export declare class RolesYamlInvalidError extends WorkflowDomainError {
33
+ readonly errors: string[];
34
+ constructor(errors: string[]);
35
+ }
36
+ /**
37
+ * True iff `workspaceRelPath` targets the KB's `roles.yaml` (the file at
38
+ * `<kbDirName>/roles.yaml`). Both the human save route and the agent's
39
+ * `LockingFilesystem` speak workspace-relative paths, so this is the form we
40
+ * key on. A bare `roles.yaml` (no KB prefix) is also accepted defensively.
41
+ */
42
+ export declare function isRolesYamlPath(workspaceRelPath: string, kbDirName: string): boolean;
43
+ /**
44
+ * Throw {@link RolesYamlInvalidError} if `content` does not parse as a valid
45
+ * `roles.yaml` per the resolver's own parser. No-op on valid content.
46
+ */
47
+ export declare function assertRolesYamlParsable(content: string): void;
48
+ /**
49
+ * A `LockingFilesystem` write-validator scoped to a KB dir: refuses a raw write
50
+ * that would leave `roles.yaml` unparseable. Pass the returned closure as the
51
+ * filesystem's `validateWrite` hook. Only string writes are checked — every
52
+ * code path that writes `roles.yaml` writes it as text; a binary write to that
53
+ * path is nonsensical and left alone.
54
+ */
55
+ export declare function makeRolesYamlWriteValidator(kbDirName: string): (path: string, content: FileContent) => void;
56
+ //# sourceMappingURL=roles-yaml-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roles-yaml-guard.d.ts","sourceRoot":"","sources":["../../../src/modules/access-model/roles-yaml-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAE1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAEpE,+EAA+E;AAC/E,eAAO,MAAM,mBAAmB,eAAe,CAAC;AAEhD;;;;GAIG;AACH,qBAAa,qBAAsB,SAAQ,mBAAmB;IAC5D,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;gBACd,MAAM,EAAE,MAAM,EAAE;CAS7B;AAOD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,gBAAgB,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAGpF;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAG7D;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CACzC,SAAS,EAAE,MAAM,GAChB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,KAAK,IAAI,CAM9C"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Pre-disk validity gate for `roles.yaml`.
3
+ *
4
+ * WHY this exists: `roles.yaml` is the single point of failure for the whole
5
+ * app. The runtime resolver's `loadModel` HARD-THROWS `AccessConfigError` on a
6
+ * parse failure, and `isAdmin` swallows that into `false` for EVERYONE — so a
7
+ * single malformed `roles.yaml` is an app-wide, in-app-unrecoverable admin
8
+ * lockout. The dedicated Roles & Members service already validates every
9
+ * candidate before writing (`assertLoadable`), but the two RAW write paths do
10
+ * not:
11
+ * - the human editor save (`PUT /workspace/:id/file`)
12
+ * - the agent's file tools (`LockingFilesystem.writeFile` / `writeFiles`)
13
+ *
14
+ * Both of those let an admin (or an admin-driven agent) commit a broken file.
15
+ * This guard plugs them: it runs the resolver's OWN parser on the candidate
16
+ * BEFORE a byte hits disk, and refuses the write with a 422 on any error. A
17
+ * write that passes here is loadable by `loadModel`, so the lockout becomes
18
+ * structurally unreachable through normal edits.
19
+ *
20
+ * The break-glass recovery path (for a file that got broken some OTHER way —
21
+ * a direct git push, a pre-guard commit) lives in `roles-admin.service.ts`.
22
+ */
23
+ import { parseRolesYaml } from './access-grammar.js';
24
+ import { WorkflowDomainError } from '../../shared/domain-errors.js';
25
+ /** Repo-relative basename of the roles file (it lives at the KB repo root). */
26
+ export const ROLES_YAML_BASENAME = 'roles.yaml';
27
+ /**
28
+ * A raw write whose candidate `roles.yaml` would not parse. 422 (bad input) —
29
+ * the edit is refused and nothing is written. Carries the parser errors so the
30
+ * editor / agent can show exactly what's wrong.
31
+ */
32
+ export class RolesYamlInvalidError extends WorkflowDomainError {
33
+ errors;
34
+ constructor(errors) {
35
+ super(`roles.yaml would be invalid and was not saved: ${errors.join('; ')}`, 422, { kind: 'roles-yaml-invalid', rolesYamlErrors: errors });
36
+ this.name = 'RolesYamlInvalidError';
37
+ this.errors = errors;
38
+ }
39
+ }
40
+ /** Normalise a workspace-relative path: backslashes → `/`, strip a leading `./`. */
41
+ function normalizeWsPath(p) {
42
+ return p.replace(/\\/g, '/').replace(/^\.\//, '');
43
+ }
44
+ /**
45
+ * True iff `workspaceRelPath` targets the KB's `roles.yaml` (the file at
46
+ * `<kbDirName>/roles.yaml`). Both the human save route and the agent's
47
+ * `LockingFilesystem` speak workspace-relative paths, so this is the form we
48
+ * key on. A bare `roles.yaml` (no KB prefix) is also accepted defensively.
49
+ */
50
+ export function isRolesYamlPath(workspaceRelPath, kbDirName) {
51
+ const norm = normalizeWsPath(workspaceRelPath);
52
+ return norm === `${kbDirName}/${ROLES_YAML_BASENAME}` || norm === ROLES_YAML_BASENAME;
53
+ }
54
+ /**
55
+ * Throw {@link RolesYamlInvalidError} if `content` does not parse as a valid
56
+ * `roles.yaml` per the resolver's own parser. No-op on valid content.
57
+ */
58
+ export function assertRolesYamlParsable(content) {
59
+ const parsed = parseRolesYaml(content);
60
+ if (!parsed.ok)
61
+ throw new RolesYamlInvalidError(parsed.errors);
62
+ }
63
+ /**
64
+ * A `LockingFilesystem` write-validator scoped to a KB dir: refuses a raw write
65
+ * that would leave `roles.yaml` unparseable. Pass the returned closure as the
66
+ * filesystem's `validateWrite` hook. Only string writes are checked — every
67
+ * code path that writes `roles.yaml` writes it as text; a binary write to that
68
+ * path is nonsensical and left alone.
69
+ */
70
+ export function makeRolesYamlWriteValidator(kbDirName) {
71
+ return (path, content) => {
72
+ if (typeof content !== 'string')
73
+ return;
74
+ if (!isRolesYamlPath(path, kbDirName))
75
+ return;
76
+ assertRolesYamlParsable(content);
77
+ };
78
+ }
79
+ //# sourceMappingURL=roles-yaml-guard.js.map