@bevel-software/platform-core-backend 0.33.0 → 0.34.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 (497) hide show
  1. package/agent-guide/access-control.md +19 -8
  2. package/agent-guide/html-views.md +231 -5
  3. package/agent-guide/tool-manuals.md +1 -1
  4. package/dist/core/catalog-revision.d.ts +12 -1
  5. package/dist/core/catalog-revision.d.ts.map +1 -1
  6. package/dist/core/catalog-revision.js +23 -3
  7. package/dist/core/catalog-revision.js.map +1 -1
  8. package/dist/core/core-ports.d.ts +41 -0
  9. package/dist/core/core-ports.d.ts.map +1 -1
  10. package/dist/core/core-ports.js.map +1 -1
  11. package/dist/core/create-core-server.d.ts.map +1 -1
  12. package/dist/core/create-core-server.js +14 -7
  13. package/dist/core/create-core-server.js.map +1 -1
  14. package/dist/core/create-core-services.d.ts +16 -5
  15. package/dist/core/create-core-services.d.ts.map +1 -1
  16. package/dist/core/create-core-services.js +37 -12
  17. package/dist/core/create-core-services.js.map +1 -1
  18. package/dist/core/lifecycle.d.ts +16 -0
  19. package/dist/core/lifecycle.d.ts.map +1 -1
  20. package/dist/core/lifecycle.js +12 -2
  21. package/dist/core/lifecycle.js.map +1 -1
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/modules/access/access-control.interface.d.ts +17 -9
  26. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  27. package/dist/modules/access/access-control.service.d.ts +12 -2
  28. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  29. package/dist/modules/access/access-control.service.js +164 -75
  30. package/dist/modules/access/access-control.service.js.map +1 -1
  31. package/dist/modules/access/access-mutation.service.d.ts +22 -24
  32. package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
  33. package/dist/modules/access/access-mutation.service.js +48 -51
  34. package/dist/modules/access/access-mutation.service.js.map +1 -1
  35. package/dist/modules/access/access-view.d.ts.map +1 -1
  36. package/dist/modules/access/access-view.js +12 -11
  37. package/dist/modules/access/access-view.js.map +1 -1
  38. package/dist/modules/access/access.routes.d.ts +3 -0
  39. package/dist/modules/access/access.routes.d.ts.map +1 -1
  40. package/dist/modules/access/access.routes.js +35 -29
  41. package/dist/modules/access/access.routes.js.map +1 -1
  42. package/dist/modules/access/groups-admin.routes.d.ts.map +1 -1
  43. package/dist/modules/access/groups-admin.routes.js +4 -2
  44. package/dist/modules/access/groups-admin.routes.js.map +1 -1
  45. package/dist/modules/access/groups-admin.service.d.ts +44 -13
  46. package/dist/modules/access/groups-admin.service.d.ts.map +1 -1
  47. package/dist/modules/access/groups-admin.service.js +80 -38
  48. package/dist/modules/access/groups-admin.service.js.map +1 -1
  49. package/dist/modules/access/groups-edit.d.ts +1 -1
  50. package/dist/modules/access/groups-edit.d.ts.map +1 -1
  51. package/dist/modules/access/groups-edit.js +1 -1
  52. package/dist/modules/access/groups-edit.js.map +1 -1
  53. package/dist/modules/access/reference-scan.d.ts +116 -24
  54. package/dist/modules/access/reference-scan.d.ts.map +1 -1
  55. package/dist/modules/access/reference-scan.js +305 -44
  56. package/dist/modules/access/reference-scan.js.map +1 -1
  57. package/dist/modules/access/roles-admin.service.d.ts +17 -8
  58. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  59. package/dist/modules/access/roles-admin.service.js +32 -23
  60. package/dist/modules/access/roles-admin.service.js.map +1 -1
  61. package/dist/modules/access-model/access-grammar.d.ts +35 -10
  62. package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
  63. package/dist/modules/access-model/access-grammar.js +60 -20
  64. package/dist/modules/access-model/access-grammar.js.map +1 -1
  65. package/dist/modules/access-model/access-splice.d.ts +22 -18
  66. package/dist/modules/access-model/access-splice.d.ts.map +1 -1
  67. package/dist/modules/access-model/access-splice.js +20 -14
  68. package/dist/modules/access-model/access-splice.js.map +1 -1
  69. package/dist/modules/access-model/group-files.d.ts +1 -0
  70. package/dist/modules/access-model/group-files.d.ts.map +1 -1
  71. package/dist/modules/access-model/group-files.js +5 -1
  72. package/dist/modules/access-model/group-files.js.map +1 -1
  73. package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -1
  74. package/dist/modules/agent-guide/agent-guide.tools.js +13 -3
  75. package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -1
  76. package/dist/modules/agent-instructions/compose.d.ts +3 -1
  77. package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
  78. package/dist/modules/agent-instructions/compose.js +3 -1
  79. package/dist/modules/agent-instructions/compose.js.map +1 -1
  80. package/dist/modules/agent-instructions/shared-file-rules.d.ts +5 -1
  81. package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
  82. package/dist/modules/agent-instructions/shared-file-rules.js +16 -1
  83. package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
  84. package/dist/modules/agent-scope/agent-scope.contract.d.ts +67 -0
  85. package/dist/modules/agent-scope/agent-scope.contract.d.ts.map +1 -0
  86. package/dist/modules/agent-scope/agent-scope.contract.js +27 -0
  87. package/dist/modules/agent-scope/agent-scope.contract.js.map +1 -0
  88. package/dist/modules/agent-scope/connection-scope.d.ts +111 -0
  89. package/dist/modules/agent-scope/connection-scope.d.ts.map +1 -0
  90. package/dist/modules/agent-scope/connection-scope.js +165 -0
  91. package/dist/modules/agent-scope/connection-scope.js.map +1 -0
  92. package/dist/modules/agent-scope/index.d.ts +3 -0
  93. package/dist/modules/agent-scope/index.d.ts.map +1 -0
  94. package/dist/modules/agent-scope/index.js +3 -0
  95. package/dist/modules/agent-scope/index.js.map +1 -0
  96. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
  97. package/dist/modules/audit/agent-audit.service.js +2 -0
  98. package/dist/modules/audit/agent-audit.service.js.map +1 -1
  99. package/dist/modules/audit/audit.contract.d.ts +6 -0
  100. package/dist/modules/audit/audit.contract.d.ts.map +1 -1
  101. package/dist/modules/audit/audit.contract.js.map +1 -1
  102. package/dist/modules/auth/account-admission.d.ts +12 -0
  103. package/dist/modules/auth/account-admission.d.ts.map +1 -1
  104. package/dist/modules/auth/account-admission.js +15 -0
  105. package/dist/modules/auth/account-admission.js.map +1 -1
  106. package/dist/modules/auth/auth.middleware.d.ts +27 -1
  107. package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
  108. package/dist/modules/auth/auth.middleware.js +42 -4
  109. package/dist/modules/auth/auth.middleware.js.map +1 -1
  110. package/dist/modules/auth/auth.routes.d.ts +13 -9
  111. package/dist/modules/auth/auth.routes.d.ts.map +1 -1
  112. package/dist/modules/auth/auth.routes.js +35 -29
  113. package/dist/modules/auth/auth.routes.js.map +1 -1
  114. package/dist/modules/auth/auth.service.d.ts +55 -6
  115. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  116. package/dist/modules/auth/auth.service.js +128 -34
  117. package/dist/modules/auth/auth.service.js.map +1 -1
  118. package/dist/modules/auth/index.d.ts +1 -1
  119. package/dist/modules/auth/index.d.ts.map +1 -1
  120. package/dist/modules/auth/index.js +1 -1
  121. package/dist/modules/auth/index.js.map +1 -1
  122. package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
  123. package/dist/modules/auth/oidc-auth-provider.js +2 -9
  124. package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
  125. package/dist/modules/database/core-schema.d.ts +68 -0
  126. package/dist/modules/database/core-schema.d.ts.map +1 -1
  127. package/dist/modules/database/core-schema.js +33 -0
  128. package/dist/modules/database/core-schema.js.map +1 -1
  129. package/dist/modules/declared-variables/declared-variables.routes.d.ts +8 -1
  130. package/dist/modules/declared-variables/declared-variables.routes.d.ts.map +1 -1
  131. package/dist/modules/declared-variables/declared-variables.routes.js +13 -2
  132. package/dist/modules/declared-variables/declared-variables.routes.js.map +1 -1
  133. package/dist/modules/embed/embed.tools.d.ts.map +1 -1
  134. package/dist/modules/embed/embed.tools.js +5 -0
  135. package/dist/modules/embed/embed.tools.js.map +1 -1
  136. package/dist/modules/kb-fs/index.d.ts +2 -0
  137. package/dist/modules/kb-fs/index.d.ts.map +1 -1
  138. package/dist/modules/kb-fs/index.js +1 -0
  139. package/dist/modules/kb-fs/index.js.map +1 -1
  140. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
  141. package/dist/modules/kb-fs/locking-filesystem.js +7 -1
  142. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
  143. package/dist/modules/kb-fs/repo-path.d.ts +26 -6
  144. package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
  145. package/dist/modules/kb-fs/repo-path.js +22 -7
  146. package/dist/modules/kb-fs/repo-path.js.map +1 -1
  147. package/dist/modules/kb-fs/tmp-ref.d.ts +36 -0
  148. package/dist/modules/kb-fs/tmp-ref.d.ts.map +1 -0
  149. package/dist/modules/kb-fs/tmp-ref.js +76 -0
  150. package/dist/modules/kb-fs/tmp-ref.js.map +1 -0
  151. package/dist/modules/mcp/mcp-auth.middleware.d.ts +7 -0
  152. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  153. package/dist/modules/mcp/mcp-auth.middleware.js +14 -1
  154. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  155. package/dist/modules/mcp/mcp.routes.d.ts +7 -1
  156. package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
  157. package/dist/modules/mcp/mcp.routes.js +39 -4
  158. package/dist/modules/mcp/mcp.routes.js.map +1 -1
  159. package/dist/modules/mcp/mcp.service.d.ts +11 -0
  160. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  161. package/dist/modules/mcp/mcp.service.js +68 -13
  162. package/dist/modules/mcp/mcp.service.js.map +1 -1
  163. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -1
  164. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  165. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +45 -10
  166. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  167. package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts +9 -0
  168. package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
  169. package/dist/modules/mcp/oauth/oauth-consent.routes.js +34 -1
  170. package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
  171. package/dist/modules/plugins/index.d.ts +1 -1
  172. package/dist/modules/plugins/index.d.ts.map +1 -1
  173. package/dist/modules/plugins/index.js.map +1 -1
  174. package/dist/modules/plugins/plugin-links.d.ts +18 -2
  175. package/dist/modules/plugins/plugin-links.d.ts.map +1 -1
  176. package/dist/modules/plugins/plugin-links.js.map +1 -1
  177. package/dist/modules/plugins/plugins.routes.d.ts +5 -1
  178. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  179. package/dist/modules/plugins/plugins.routes.js +32 -5
  180. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  181. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +9 -0
  182. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
  183. package/dist/modules/secrets-vault/secrets-vault.routes.js +15 -3
  184. package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
  185. package/dist/modules/skills/index.d.ts +3 -3
  186. package/dist/modules/skills/index.d.ts.map +1 -1
  187. package/dist/modules/skills/index.js +0 -2
  188. package/dist/modules/skills/index.js.map +1 -1
  189. package/dist/modules/skills/skills.contract.d.ts +16 -0
  190. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  191. package/dist/modules/skills/skills.service.d.ts.map +1 -1
  192. package/dist/modules/skills/skills.service.js +6 -1
  193. package/dist/modules/skills/skills.service.js.map +1 -1
  194. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  195. package/dist/modules/skills/skills.tools.js +24 -12
  196. package/dist/modules/skills/skills.tools.js.map +1 -1
  197. package/dist/modules/tool-auth/external-api-key.interface.d.ts +11 -0
  198. package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
  199. package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
  200. package/dist/modules/tool-auth/external-api-key.service.d.ts +1 -0
  201. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  202. package/dist/modules/tool-auth/external-api-key.service.js +4 -1
  203. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  204. package/dist/modules/tool-auth/index.d.ts +1 -1
  205. package/dist/modules/tool-auth/index.d.ts.map +1 -1
  206. package/dist/modules/tool-auth/internal-token.service.d.ts +9 -0
  207. package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
  208. package/dist/modules/tool-auth/internal-token.service.js +1 -0
  209. package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
  210. package/dist/modules/tool-auth/tool-auth.middleware.d.ts +27 -3
  211. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  212. package/dist/modules/tool-auth/tool-auth.middleware.js +32 -6
  213. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  214. package/dist/modules/tool-helpers/tool-context.d.ts +7 -0
  215. package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
  216. package/dist/modules/tool-helpers/tool-context.js +3 -0
  217. package/dist/modules/tool-helpers/tool-context.js.map +1 -1
  218. package/dist/modules/tool-helpers/tool-handler.d.ts +15 -0
  219. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  220. package/dist/modules/tool-helpers/tool-handler.js +19 -0
  221. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  222. package/dist/modules/tool-helpers/tool.contract.d.ts +10 -0
  223. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  224. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  225. package/dist/modules/tool-helpers/validate-token.d.ts +3 -1
  226. package/dist/modules/tool-helpers/validate-token.d.ts.map +1 -1
  227. package/dist/modules/tool-helpers/validate-token.js +1 -1
  228. package/dist/modules/tool-helpers/validate-token.js.map +1 -1
  229. package/dist/modules/tool-manuals/index.d.ts +3 -3
  230. package/dist/modules/tool-manuals/index.d.ts.map +1 -1
  231. package/dist/modules/tool-manuals/index.js +0 -2
  232. package/dist/modules/tool-manuals/index.js.map +1 -1
  233. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +19 -6
  234. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  235. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +7 -1
  236. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
  237. package/dist/modules/tool-manuals/tool-manuals.routes.js +41 -4
  238. package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
  239. package/dist/modules/tool-manuals/tool-manuals.service.d.ts +13 -6
  240. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  241. package/dist/modules/tool-manuals/tool-manuals.service.js +23 -15
  242. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  243. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  244. package/dist/modules/tool-manuals/tool-manuals.tools.js +25 -10
  245. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  246. package/dist/modules/tool-registry/manual.routes.d.ts +8 -1
  247. package/dist/modules/tool-registry/manual.routes.d.ts.map +1 -1
  248. package/dist/modules/tool-registry/manual.routes.js +27 -7
  249. package/dist/modules/tool-registry/manual.routes.js.map +1 -1
  250. package/dist/modules/tool-registry/tool.contract.d.ts +7 -0
  251. package/dist/modules/tool-registry/tool.contract.d.ts.map +1 -1
  252. package/dist/modules/tool-registry/tool.contract.js.map +1 -1
  253. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  254. package/dist/modules/workflow/agent-tools/workflow.tools.js +14 -9
  255. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  256. package/dist/modules/workflow/git/git.service.d.ts +2 -2
  257. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  258. package/dist/modules/workflow/git/git.service.js +20 -35
  259. package/dist/modules/workflow/git/git.service.js.map +1 -1
  260. package/dist/modules/workflow/workflow.service.d.ts +2 -1
  261. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  262. package/dist/modules/workflow/workflow.service.js +3 -0
  263. package/dist/modules/workflow/workflow.service.js.map +1 -1
  264. package/dist/modules/workspace/file-readers/doc-extract.service.d.ts +28 -4
  265. package/dist/modules/workspace/file-readers/doc-extract.service.d.ts.map +1 -1
  266. package/dist/modules/workspace/file-readers/doc-extract.service.js +61 -40
  267. package/dist/modules/workspace/file-readers/doc-extract.service.js.map +1 -1
  268. package/dist/modules/workspace/index.d.ts +8 -0
  269. package/dist/modules/workspace/index.d.ts.map +1 -1
  270. package/dist/modules/workspace/index.js +8 -0
  271. package/dist/modules/workspace/index.js.map +1 -1
  272. package/dist/modules/workspace/nul-scan.d.ts +19 -0
  273. package/dist/modules/workspace/nul-scan.d.ts.map +1 -0
  274. package/dist/modules/workspace/nul-scan.js +55 -0
  275. package/dist/modules/workspace/nul-scan.js.map +1 -0
  276. package/dist/modules/workspace/pattern-matcher.contract.d.ts +110 -27
  277. package/dist/modules/workspace/pattern-matcher.contract.d.ts.map +1 -1
  278. package/dist/modules/workspace/pattern-matcher.contract.js +30 -13
  279. package/dist/modules/workspace/pattern-matcher.contract.js.map +1 -1
  280. package/dist/modules/workspace/ripgrep-search.d.ts +54 -0
  281. package/dist/modules/workspace/ripgrep-search.d.ts.map +1 -0
  282. package/dist/modules/workspace/ripgrep-search.js +657 -0
  283. package/dist/modules/workspace/ripgrep-search.js.map +1 -0
  284. package/dist/modules/workspace/ripgrep-version.d.ts +15 -0
  285. package/dist/modules/workspace/ripgrep-version.d.ts.map +1 -0
  286. package/dist/modules/workspace/ripgrep-version.js +31 -0
  287. package/dist/modules/workspace/ripgrep-version.js.map +1 -0
  288. package/dist/modules/workspace/startup/beside-checkout.d.ts +8 -5
  289. package/dist/modules/workspace/startup/beside-checkout.d.ts.map +1 -1
  290. package/dist/modules/workspace/startup/beside-checkout.js +14 -5
  291. package/dist/modules/workspace/startup/beside-checkout.js.map +1 -1
  292. package/dist/modules/workspace/worker-pool.d.ts +3 -4
  293. package/dist/modules/workspace/worker-pool.d.ts.map +1 -1
  294. package/dist/modules/workspace/worker-pool.js.map +1 -1
  295. package/dist/modules/workspace/workspace-tmp.d.ts +146 -0
  296. package/dist/modules/workspace/workspace-tmp.d.ts.map +1 -0
  297. package/dist/modules/workspace/workspace-tmp.js +435 -0
  298. package/dist/modules/workspace/workspace-tmp.js.map +1 -0
  299. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  300. package/dist/modules/workspace/workspace.routes.js +5 -26
  301. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  302. package/dist/modules/workspace/workspace.service.d.ts +47 -6
  303. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  304. package/dist/modules/workspace/workspace.service.js +86 -6
  305. package/dist/modules/workspace/workspace.service.js.map +1 -1
  306. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  307. package/dist/modules/workspace/workspace.tools.js +460 -116
  308. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  309. package/dist/shared/path-order.d.ts +28 -8
  310. package/dist/shared/path-order.d.ts.map +1 -1
  311. package/dist/shared/path-order.js +37 -8
  312. package/dist/shared/path-order.js.map +1 -1
  313. package/migrations/0022_session_version.sql +6 -0
  314. package/migrations/0023_agent_path.sql +3 -0
  315. package/migrations/meta/0022_snapshot.json +2360 -0
  316. package/migrations/meta/0023_snapshot.json +2378 -0
  317. package/migrations/meta/_journal.json +14 -0
  318. package/package.json +3 -3
  319. package/src/__tests__/pg-scratch.test.ts +69 -0
  320. package/src/__tests__/pg-scratch.ts +31 -0
  321. package/src/__tests__/slow-ripgrep.ts +65 -0
  322. package/src/core/__tests__/boot-without-ripgrep.test.ts +34 -0
  323. package/src/core/__tests__/catalog-revision.test.ts +89 -0
  324. package/src/core/__tests__/damaged-document.tools.test.ts +2 -2
  325. package/src/core/__tests__/gated-boot.test.ts +8 -1
  326. package/src/core/__tests__/lifecycle.test.ts +46 -13
  327. package/src/core/catalog-revision.ts +24 -2
  328. package/src/core/core-ports.ts +42 -0
  329. package/src/core/create-core-server.ts +13 -0
  330. package/src/core/create-core-services.ts +49 -12
  331. package/src/core/lifecycle.ts +26 -2
  332. package/src/index.ts +1 -1
  333. package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +12 -0
  334. package/src/modules/access/__tests__/access-control.colliding-name-cases.test.ts +113 -0
  335. package/src/modules/access/__tests__/access-control.frontmatter-head.test.ts +117 -0
  336. package/src/modules/access/__tests__/access-control.group-named-admin.test.ts +122 -0
  337. package/src/modules/access/__tests__/access-control.preview-relocation.test.ts +50 -0
  338. package/src/modules/access/__tests__/access-declarations.test.ts +19 -0
  339. package/src/modules/access/__tests__/access-groups.test.ts +55 -22
  340. package/src/modules/access/__tests__/access-mutation.service.test.ts +85 -59
  341. package/src/modules/access/__tests__/access-restrict-here.test.ts +7 -5
  342. package/src/modules/access/__tests__/access.routes.admins.test.ts +1 -0
  343. package/src/modules/access/__tests__/access.routes.batch.test.ts +2 -0
  344. package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +2 -0
  345. package/src/modules/access/__tests__/access.routes.group-grant.test.ts +50 -14
  346. package/src/modules/access/__tests__/access.routes.overrides.test.ts +2 -1
  347. package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +1 -0
  348. package/src/modules/access/__tests__/access.routes.prospective.test.ts +2 -0
  349. package/src/modules/access/__tests__/access.routes.revoke.test.ts +18 -13
  350. package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +2 -0
  351. package/src/modules/access/__tests__/grant-sources.test.ts +20 -15
  352. package/src/modules/access/__tests__/groups-admin.routes.test.ts +61 -0
  353. package/src/modules/access/__tests__/groups-admin.service.test.ts +211 -18
  354. package/src/modules/access/__tests__/reference-scan.test.ts +366 -14
  355. package/src/modules/access/__tests__/roles-admin.security.test.ts +1 -1
  356. package/src/modules/access/__tests__/roles-admin.service.test.ts +43 -22
  357. package/src/modules/access/__tests__/roles-capabilities.test.ts +6 -2
  358. package/src/modules/access/__tests__/roles.routes.test.ts +1 -1
  359. package/src/modules/access/access-control.interface.ts +17 -10
  360. package/src/modules/access/access-control.service.ts +158 -72
  361. package/src/modules/access/access-mutation.service.ts +48 -53
  362. package/src/modules/access/access-view.ts +12 -10
  363. package/src/modules/access/access.routes.ts +43 -33
  364. package/src/modules/access/groups-admin.routes.ts +4 -2
  365. package/src/modules/access/groups-admin.service.ts +114 -44
  366. package/src/modules/access/groups-edit.ts +1 -1
  367. package/src/modules/access/reference-scan.ts +319 -46
  368. package/src/modules/access/roles-admin.service.ts +43 -26
  369. package/src/modules/access/synced-groups-writer.ts +1 -1
  370. package/src/modules/access-model/__tests__/access-grammar.test.ts +38 -1
  371. package/src/modules/access-model/__tests__/access-splice.test.ts +50 -8
  372. package/src/modules/access-model/access-grammar.ts +65 -20
  373. package/src/modules/access-model/access-splice.ts +31 -23
  374. package/src/modules/access-model/group-files.ts +5 -0
  375. package/src/modules/agent-guide/__tests__/agent-guide.test.ts +31 -0
  376. package/src/modules/agent-guide/agent-guide.tools.ts +13 -2
  377. package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +1 -0
  378. package/src/modules/agent-instructions/compose.ts +3 -1
  379. package/src/modules/agent-instructions/shared-file-rules.ts +17 -1
  380. package/src/modules/agent-scope/__tests__/agent-surfaces.connect-as.test.ts +421 -0
  381. package/src/modules/agent-scope/__tests__/connection-scope.test.ts +46 -0
  382. package/src/modules/agent-scope/__tests__/in-memory-kb.ts +148 -0
  383. package/src/modules/agent-scope/agent-scope.contract.ts +83 -0
  384. package/src/modules/agent-scope/connection-scope.ts +199 -0
  385. package/src/modules/agent-scope/index.ts +15 -0
  386. package/src/modules/audit/agent-audit.service.ts +2 -0
  387. package/src/modules/audit/audit.contract.ts +6 -0
  388. package/src/modules/auth/__tests__/auth.middleware.test.ts +30 -2
  389. package/src/modules/auth/__tests__/auth.routes.test.ts +71 -5
  390. package/src/modules/auth/__tests__/auth.service.test.ts +90 -9
  391. package/src/modules/auth/__tests__/password-change-sessions.pg.test.ts +269 -0
  392. package/src/modules/auth/account-admission.ts +19 -0
  393. package/src/modules/auth/auth.middleware.ts +45 -4
  394. package/src/modules/auth/auth.routes.ts +46 -29
  395. package/src/modules/auth/auth.service.ts +135 -34
  396. package/src/modules/auth/index.ts +2 -0
  397. package/src/modules/auth/oidc-auth-provider.ts +2 -9
  398. package/src/modules/database/__tests__/session-version-migration.pg.test.ts +133 -0
  399. package/src/modules/database/core-schema.ts +33 -0
  400. package/src/modules/declared-variables/__tests__/declared-variables.route.test.ts +44 -1
  401. package/src/modules/declared-variables/declared-variables.routes.ts +12 -1
  402. package/src/modules/embed/__tests__/embed.tools.test.ts +12 -0
  403. package/src/modules/embed/embed.tools.ts +5 -0
  404. package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +25 -0
  405. package/src/modules/kb-fs/__tests__/repo-path.test.ts +53 -8
  406. package/src/modules/kb-fs/index.ts +2 -0
  407. package/src/modules/kb-fs/locking-filesystem.ts +7 -1
  408. package/src/modules/kb-fs/repo-path.ts +33 -7
  409. package/src/modules/kb-fs/tmp-ref.ts +81 -0
  410. package/src/modules/mcp/__tests__/connect-as.pg.test.ts +137 -0
  411. package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +29 -5
  412. package/src/modules/mcp/__tests__/mcp-routes-harness.ts +8 -0
  413. package/src/modules/mcp/__tests__/mcp.connect-as.test.ts +195 -0
  414. package/src/modules/mcp/__tests__/mcp.routes.connect-as.test.ts +192 -0
  415. package/src/modules/mcp/__tests__/oauth-consent.connect-as.test.ts +146 -0
  416. package/src/modules/mcp/mcp-auth.middleware.ts +19 -2
  417. package/src/modules/mcp/mcp.routes.ts +39 -3
  418. package/src/modules/mcp/mcp.service.ts +107 -17
  419. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +46 -8
  420. package/src/modules/mcp/oauth/oauth-consent.routes.ts +41 -1
  421. package/src/modules/plugins/__tests__/plugins.tools.test.ts +38 -2
  422. package/src/modules/plugins/index.ts +1 -1
  423. package/src/modules/plugins/plugin-links.ts +19 -2
  424. package/src/modules/plugins/plugins.routes.ts +35 -4
  425. package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +113 -0
  426. package/src/modules/secrets-vault/secrets-vault.routes.ts +23 -3
  427. package/src/modules/skills/__tests__/skills.service.test.ts +34 -0
  428. package/src/modules/skills/index.ts +7 -2
  429. package/src/modules/skills/skills.contract.ts +17 -0
  430. package/src/modules/skills/skills.service.ts +5 -1
  431. package/src/modules/skills/skills.tools.ts +44 -13
  432. package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +4 -0
  433. package/src/modules/tool-auth/external-api-key.interface.ts +11 -1
  434. package/src/modules/tool-auth/external-api-key.service.ts +5 -2
  435. package/src/modules/tool-auth/index.ts +1 -1
  436. package/src/modules/tool-auth/internal-token.service.ts +10 -0
  437. package/src/modules/tool-auth/tool-auth.middleware.ts +45 -3
  438. package/src/modules/tool-helpers/__tests__/can-write.tools.test.ts +3 -1
  439. package/src/modules/tool-helpers/tool-context.ts +10 -0
  440. package/src/modules/tool-helpers/tool-handler.ts +34 -0
  441. package/src/modules/tool-helpers/tool.contract.ts +10 -0
  442. package/src/modules/tool-helpers/validate-token.ts +4 -2
  443. package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +80 -4
  444. package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +42 -0
  445. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +3 -3
  446. package/src/modules/tool-manuals/index.ts +6 -2
  447. package/src/modules/tool-manuals/tool-manuals.contract.ts +18 -6
  448. package/src/modules/tool-manuals/tool-manuals.routes.ts +40 -3
  449. package/src/modules/tool-manuals/tool-manuals.service.ts +31 -15
  450. package/src/modules/tool-manuals/tool-manuals.tools.ts +28 -11
  451. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +2 -2
  452. package/src/modules/tool-registry/manual.routes.ts +26 -5
  453. package/src/modules/tool-registry/tool.contract.ts +7 -0
  454. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +1 -1
  455. package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +1 -1
  456. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +54 -3
  457. package/src/modules/workflow/agent-tools/workflow.tools.ts +14 -9
  458. package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +130 -3
  459. package/src/modules/workflow/git/git.service.ts +29 -48
  460. package/src/modules/workflow/workflow.service.ts +5 -0
  461. package/src/modules/workspace/__tests__/git-internals.security.test.ts +2 -2
  462. package/src/modules/workspace/__tests__/holds-nul.test.ts +66 -0
  463. package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +3 -3
  464. package/src/modules/workspace/__tests__/ripgrep-search.test.ts +570 -0
  465. package/src/modules/workspace/__tests__/ripgrep-version.test.ts +29 -0
  466. package/src/modules/workspace/__tests__/workspace-tmp.tools.test.ts +928 -0
  467. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +17 -37
  468. package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +20 -8
  469. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +2 -2
  470. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +22 -10
  471. package/src/modules/workspace/__tests__/workspace.tools.test.ts +393 -113
  472. package/src/modules/workspace/file-readers/__tests__/doc-extract.service.test.ts +10 -0
  473. package/src/modules/workspace/file-readers/doc-extract.service.ts +71 -38
  474. package/src/modules/workspace/index.ts +20 -0
  475. package/src/modules/workspace/nul-scan.ts +50 -0
  476. package/src/modules/workspace/pattern-matcher.contract.ts +123 -32
  477. package/src/modules/workspace/ripgrep-search.ts +715 -0
  478. package/src/modules/workspace/ripgrep-version.ts +35 -0
  479. package/src/modules/workspace/startup/__tests__/beside-checkout.test.ts +11 -0
  480. package/src/modules/workspace/startup/beside-checkout.ts +13 -5
  481. package/src/modules/workspace/worker-pool.ts +3 -4
  482. package/src/modules/workspace/workspace-tmp.ts +457 -0
  483. package/src/modules/workspace/workspace.routes.ts +6 -27
  484. package/src/modules/workspace/workspace.service.ts +92 -15
  485. package/src/modules/workspace/workspace.tools.ts +500 -155
  486. package/src/shared/path-order.ts +37 -8
  487. package/starter-packs/engineering/Plugins/engineering-starter/access.md +1 -1
  488. package/starter-packs/general/Plugins/general-starter/access.md +1 -1
  489. package/starter-packs/operations/Plugins/operations-starter/access.md +1 -1
  490. package/starter-packs/sales/Plugins/sales-starter/access.md +1 -1
  491. package/starter-packs/support/Plugins/support-starter/access.md +1 -1
  492. package/dist/modules/workspace/bounded-matcher.d.ts +0 -73
  493. package/dist/modules/workspace/bounded-matcher.d.ts.map +0 -1
  494. package/dist/modules/workspace/bounded-matcher.js +0 -200
  495. package/dist/modules/workspace/bounded-matcher.js.map +0 -1
  496. package/src/modules/workspace/__tests__/bounded-matcher.test.ts +0 -213
  497. package/src/modules/workspace/bounded-matcher.ts +0 -271
@@ -1,5 +1,6 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
+ import { StringDecoder } from 'node:string_decoder';
3
4
  import { logger } from '../../shared/logging.js';
4
5
  const log = logger('access');
5
6
  import { isAbsence } from '../../shared/fs.contract.js';
@@ -7,10 +8,8 @@ import { assertOneSpecPerLine, isCommitSha, isGitTimeout } from '../../shared/gi
7
8
  import { hashEmail } from '../../shared/email-identity.js';
8
9
  import { NodeGitRunner } from '../workflow/index.js';
9
10
  import { readRevSha } from '../kb-fs/index.js';
10
- import { DEFAULT_KB_LAYOUT, PLUGIN_MANIFEST_FILE, isPersonalPluginDir, pluginIdentityOf, platformRestoreDestination, } from '@bevel-software/platform-shared';
11
- import { AccessConfigError, AccessUnreadableError, synthesizePluginPrincipals } from '../access-model/index.js';
12
- import { GROUPS_YAML, SYNCED_GROUPS_YAML, parseGroupsFile, } from '../access-model/index.js';
13
- import { ADMIN_CANONICAL, EVERYONE_CANONICAL, EVERYONE_DISPLAY, KNOWN_VERBS, ROLE_TOKEN_PREFIX, accessMdSelfEntries, canonicalEmail, canonicalRoleName, isAccessMdPath, mergeGroupsIntoRoles, parseAccessFile, parseOwnAccessEntries, parseRolesYaml, requiredVerbsFor, sourceVerbsFor, } from '../access-model/index.js';
11
+ import { DEFAULT_KB_LAYOUT, PLUGIN_MANIFEST_FILE, isFrontmatterFence, isPersonalPluginDir, pluginIdentityOf, platformRestoreDestination, } from '@bevel-software/platform-shared';
12
+ import { AccessConfigError, AccessUnreadableError, synthesizePluginPrincipals, GROUPS_YAML, SYNCED_GROUPS_YAML, parseGroupsFile, ADMIN_CANONICAL, EVERYONE_CANONICAL, EVERYONE_DISPLAY, KNOWN_VERBS, ROLE_TOKEN_PREFIX, GROUP_TOKEN_PREFIX, splitPrincipalToken, accessMdSelfEntries, canonicalEmail, canonicalRoleName, isAccessMdPath, mergeGroupsIntoRoles, parseAccessFile, parseOwnAccessEntries, parseRolesYaml, requiredVerbsFor, sourceVerbsFor, } from '../access-model/index.js';
14
13
  import { DIRECTORY_SYNC_BOT_EMAIL, DIRECTORY_SYNC_BOT_NAME, } from './directory-sync-bot.js';
15
14
  /**
16
15
  * Read many objects from a git repo in ONE `git cat-file --batch` process.
@@ -387,18 +386,22 @@ function addDerivedKeys(model, keys) {
387
386
  * twin of `principalKeysOf`: a member's key set minus everything they hold
388
387
  * for reasons of their own. `null` when no group of that name exists.
389
388
  *
390
- * Groups are keyed by their bare canonical name (`mergeGroupsIntoRoles`);
391
- * roles by `role/<name>`. A bare key whose record is a ROLE is not a group,
392
- * however it was spelled.
389
+ * Groups are keyed by `group/<name>`, and by their bare canonical name
390
+ * when no role owns it (`mergeGroupsIntoRoles`); roles by the bare name and
391
+ * `role/<name>`. `group` may be spelled either way: a bare name whose record
392
+ * is a ROLE still finds the group through its `group/` alias, because this
393
+ * question is always asked of the group.
393
394
  */
394
395
  function principalKeysOfGroup(model, group) {
395
- const canonical = canonicalRoleName(group);
396
- const record = model.roles.byCanonical.get(canonical);
396
+ const { name } = splitPrincipalToken(canonicalRoleName(group));
397
+ const record = model.roles.byCanonical.get(`${GROUP_TOKEN_PREFIX}${name}`);
397
398
  if (!record || record.kind !== 'group')
398
399
  return null;
399
- const keys = new Set([canonical]);
400
+ const keys = new Set();
400
401
  for (const [key, principal] of model.roles.byCanonical) {
401
- if (principal.kind === 'role' && principal.groupRefs?.has(canonical))
402
+ if (principal === record)
403
+ keys.add(key);
404
+ else if (principal.kind === 'role' && principal.groupRefs?.has(name))
402
405
  keys.add(key);
403
406
  }
404
407
  // Plugin principals were expanded from role/group entries; a plugin that
@@ -454,9 +457,9 @@ function isAdminEmail(model, email) {
454
457
  if (model.deploymentOwners.has(email))
455
458
  return true;
456
459
  const roles = model.roles.byEmail.get(email);
457
- // Check the explicit `role/admin` alias, NOT the bare token: bare-name
458
- // precedence is group-first, so a group that happens to be named "Admin"
459
- // owns the bare key — and its members must never inherit the capability.
460
+ // Check the explicit `role/admin` alias: it is the role's whatever the
461
+ // bare key holds, and a group that happens to be named "Admin" is only
462
+ // ever `group/admin` — its members never inherit the capability.
460
463
  return !!roles && roles.has(ADMIN_ROLE_KEY);
461
464
  }
462
465
  /**
@@ -649,26 +652,11 @@ function resolveGrantSourcesForVerb(model, verb, kind, relativePath, principal,
649
652
  const out = [];
650
653
  if (principal.kind === 'role') {
651
654
  // A named principal can be spelled two ways in a file: the bare token and
652
- // the explicit `role/<name>` token. WHICH spellings are THIS principal's
653
- // entries depends on who owns the bare key in the merged index:
654
- //
655
- // - UNSHADOWED (no group named `<bare>`): both spellings resolve to the
656
- // role, so both count — a grant under either adds a source; a deny
657
- // under either cuts off farther grants. (The revoke splice strips both
658
- // spellings in this case too, so classification and removal agree.)
659
- // - SHADOWED (a group owns the bare key): the spellings are DIFFERENT
660
- // principals. A bare-token principal is the GROUP — only bare entries
661
- // are its own; a `role/`-token principal is the ROLE — only `role/`
662
- // entries are its own. Counting the other spelling would attribute a
663
- // shadowed bare token to the role (hiding a real `role/<name>`
664
- // ancestor grant and making its revoke a false no-op), or vice versa.
665
- //
666
- // - PINNED EXACT (`tokenMatch: 'exact'`): only the literally-spelled
667
- // token is this principal's entry, shadowing notwithstanding. The
668
- // caller pinned the same identity into the splice it is checking —
669
- // a GROUP whose group has VANISHED reads "unshadowed" here, and the
670
- // alias-tolerant pair would then attribute a same-named role's
671
- // surviving `role/<name>` grant to the group.
655
+ // its explicit `role/<name>` or `group/<name>` token. Which spellings
656
+ // are THIS principal's entries is `ownTokensOf`'s one rule — the bare
657
+ // token counts only when the bare key is this principal's (the role
658
+ // when one exists, else the group), and a pinned `'exact'` counts the
659
+ // literal spelling alone.
672
660
  const tokens = ownTokensOf(model, principal.role, tokenMatch);
673
661
  for (const scope of scopes) {
674
662
  const states = tokens.map((t) => scope.byRole.get(t));
@@ -698,20 +686,37 @@ function resolveGrantSourcesForVerb(model, verb, kind, relativePath, principal,
698
686
  }
699
687
  /**
700
688
  * WHICH token spellings in a file are THIS role-shaped principal's own entries
701
- * — the shadowing rule, in one place because the grant walk, the denial walk
702
- * and the revoke splice must all agree on it (a disagreement attributes a
703
- * shadowed bare token to the wrong principal, which is how a "restrict" silently
704
- * strips a same-named role's grant). See `resolveGrantSourcesForVerb` for the
705
- * full reasoning behind each branch.
689
+ * — in one place because the grant walk, the denial walk and the revoke splice
690
+ * must all agree on it (a disagreement attributes a bare token to the wrong
691
+ * principal, which is how a "restrict" silently strips a same-named role's
692
+ * grant).
693
+ *
694
+ * - `role/X` is the role: its own entries are `role/X`, plus the bare `X`
695
+ * unless a GROUP owns the bare key (no role X exists, a group X does).
696
+ * - `group/X` is the group: `group/X`, plus the bare `X` unless a ROLE
697
+ * owns it — under role-first precedence a same-named role always does.
698
+ * - A bare `X` is whoever owns the bare key, with that kind's explicit
699
+ * spelling; a bare token with no record (`everyone`, a plugin token, a
700
+ * vanished principal) is just itself plus the `role/` spelling a legacy
701
+ * role grant may carry.
702
+ * - PINNED EXACT (`tokenMatch: 'exact'`): only the literal spelling. The
703
+ * caller pinned the same identity into the splice it is checking.
706
704
  */
707
705
  function ownTokensOf(model, role, tokenMatch) {
708
706
  const canonical = canonicalRoleName(role);
709
- const explicit = canonical.startsWith(ROLE_TOKEN_PREFIX);
710
- const bare = explicit ? canonical.slice(ROLE_TOKEN_PREFIX.length) : canonical;
711
- const shadowed = model.roles.byCanonical.get(bare)?.kind === 'group';
712
- return tokenMatch === 'exact' || shadowed
713
- ? [explicit ? `${ROLE_TOKEN_PREFIX}${bare}` : bare]
714
- : [bare, `${ROLE_TOKEN_PREFIX}${bare}`];
707
+ if (tokenMatch === 'exact')
708
+ return [canonical];
709
+ const { prefix, name } = splitPrincipalToken(canonical);
710
+ const bareOwner = model.roles.byCanonical.get(name)?.kind;
711
+ if (prefix === 'role')
712
+ return bareOwner === 'group' ? [canonical] : [name, canonical];
713
+ if (prefix === 'group')
714
+ return bareOwner === 'role' ? [canonical] : [name, canonical];
715
+ if (bareOwner === 'group')
716
+ return [name, `${GROUP_TOKEN_PREFIX}${name}`];
717
+ if (bareOwner === 'plugin')
718
+ return [name];
719
+ return [name, `${ROLE_TOKEN_PREFIX}${name}`];
715
720
  }
716
721
  /**
717
722
  * The polarity twin of {@link resolveGrantSourcesForVerb}: every file scope
@@ -836,6 +841,73 @@ function parentDirOf(relativePath) {
836
841
  const cut = relativePath.lastIndexOf('/');
837
842
  return cut === -1 ? '' : relativePath.slice(0, cut);
838
843
  }
844
+ /** The first read of {@link frontmatterHead}; each later one doubles, up to the last. */
845
+ const HEAD_FIRST_READ = 1_024;
846
+ const HEAD_LAST_READ = 64 * 1_024;
847
+ /**
848
+ * The start of `file` that holds everything its frontmatter can say: its
849
+ * first line when that is no fence (no frontmatter at all), the text through
850
+ * the closing fence when there is one, and the whole file when an opening
851
+ * fence is never closed. Scanned for its frontmatter (`scanFrontmatter`), it
852
+ * answers exactly as the whole file does, by the same fence rule
853
+ * (`isFrontmatterFence`) — and a file with no frontmatter costs one small
854
+ * read however long it is.
855
+ */
856
+ async function frontmatterHead(file) {
857
+ const handle = await fs.open(file, 'r');
858
+ try {
859
+ const decoder = new StringDecoder('utf8');
860
+ // What was read, kept as it was decoded and joined once, when answered:
861
+ // each character is scanned once, however long a line runs.
862
+ const pieces = [];
863
+ let before = 0;
864
+ // The line being read so far, folded (see `foldLine`), or null once it
865
+ // can no longer be a fence.
866
+ let line = '';
867
+ let first = true;
868
+ let position = 0;
869
+ for (let size = HEAD_FIRST_READ;; size = Math.min(size * 2, HEAD_LAST_READ)) {
870
+ const chunk = Buffer.allocUnsafe(size);
871
+ const { bytesRead } = await handle.read(chunk, 0, size, position);
872
+ if (bytesRead === 0)
873
+ return pieces.join('') + decoder.end();
874
+ position += bytesRead;
875
+ const piece = decoder.write(chunk.subarray(0, bytesRead));
876
+ pieces.push(piece);
877
+ let from = 0;
878
+ for (let nl = piece.indexOf('\n'); nl !== -1; nl = piece.indexOf('\n', from)) {
879
+ const fence = line !== null && isFrontmatterFence(foldLine(line, piece.slice(from, nl)));
880
+ // The first line opens a block or there is none; after it, the first
881
+ // fence closes the block.
882
+ if (first ? !fence : fence)
883
+ return pieces.join('').slice(0, before + nl);
884
+ first = false;
885
+ line = '';
886
+ from = nl + 1;
887
+ }
888
+ if (line !== null) {
889
+ line = foldLine(line, piece.slice(from));
890
+ if (!'---'.startsWith(line.trim()))
891
+ line = null;
892
+ }
893
+ // A first line still unfinished that already cannot be a fence.
894
+ if (first && line === null)
895
+ return pieces.join('');
896
+ before += piece.length;
897
+ }
898
+ }
899
+ finally {
900
+ await handle.close();
901
+ }
902
+ }
903
+ /**
904
+ * `line` continued by `more`, every run of whitespace folded to one space.
905
+ * Trimmed, it is `---` exactly when the unfolded line is, so it judges a fence
906
+ * as `isFrontmatterFence` does while staying a few characters long.
907
+ */
908
+ function foldLine(line, more) {
909
+ return `${line}${more}`.replace(/\s+/g, ' ');
910
+ }
839
911
  /**
840
912
  * One file's own-entries, parsed as the path `asName` would be read.
841
913
  *
@@ -946,15 +1018,15 @@ function eligibleHoldersResolved(model, verb, relativePath, fileOwn) {
946
1018
  const { byRole, byEmail, everyone } = resolveAtPath(model, verb, relativePath, fileOwn);
947
1019
  // (kind, display name) → one entry. The `byRole` keys are the merged
948
1020
  // index's canonical tokens exactly as granted (bare, or the
949
- // `role/<canonical>` alias); the `byCanonical` record a key hits carries
950
- // its kind — a `role/` alias always hits the role record, a bare key hits
951
- // whichever principal owns it under group-first precedence. A token with no
952
- // record (the built-in `everyone`, or a grant naming a since-vanished
953
- // principal) degrades to 'role', the pre-groups display. When one display
954
- // name is granted as BOTH (a role via its `role/` alias plus a same-named
955
- // group via the bare token) BOTH entries survive — they are DIFFERENT
956
- // principals, and collapsing them to one would hide the role's live
957
- // `role/<name>` grant from every consumer of the eligible list.
1021
+ // `role/` or `group/` alias); the `byCanonical` record a key hits carries
1022
+ // its kind — a `role/` alias always hits the role record, a `group/` alias
1023
+ // the group record, a bare key the role when one exists, else the group. A
1024
+ // token with no record (the built-in `everyone`, or a grant naming a
1025
+ // since-vanished principal) degrades to 'role', the pre-groups display.
1026
+ // When one display name is granted as BOTH (the role plus a same-named
1027
+ // group via `group/`) BOTH entries survive — they are DIFFERENT
1028
+ // principals, and collapsing them to one would hide one of the grants from
1029
+ // every consumer of the eligible list.
958
1030
  const byIdentity = new Map();
959
1031
  const addPrincipal = (name, kind) => {
960
1032
  const key = `${kind}\0${name.toLowerCase()}`;
@@ -997,9 +1069,9 @@ function eligibleHoldersResolved(model, verb, relativePath, fileOwn) {
997
1069
  // access.md (it'd say "restricted to no one" when Admin is the answer).
998
1070
  if (verb === 'write' &&
999
1071
  (relativePath === 'roles.yaml' || isAccessMdPath(relativePath))) {
1000
- // Look the Admin ROLE up via its explicit alias — the bare key may be
1001
- // owned by a same-named group under group-first precedence. The override
1002
- // is the ROLE's capability, so the row's kind is 'role' regardless.
1072
+ // Look the Admin ROLE up via its explicit alias, the key that is always
1073
+ // the role's. The override is the ROLE's capability, so the row's kind is
1074
+ // 'role' regardless.
1003
1075
  const adminRole = model.roles.byCanonical.get(ADMIN_ROLE_KEY);
1004
1076
  addPrincipal(adminRole ? adminRole.displayName : ADMIN_CANONICAL, 'role');
1005
1077
  }
@@ -1323,7 +1395,11 @@ export class AccessControlService {
1323
1395
  const model = await this.loadModel(workspaceId);
1324
1396
  const email = canonicalEmail(userEmail);
1325
1397
  const names = [];
1326
- for (const principal of model.roles.byCanonical.values()) {
1398
+ // Each group once, through its `group/` alias (the bare key, when the
1399
+ // group owns it, is the same record).
1400
+ for (const [key, principal] of model.roles.byCanonical) {
1401
+ if (!key.startsWith(GROUP_TOKEN_PREFIX))
1402
+ continue;
1327
1403
  if (principal.kind === 'group' && principal.emails.has(email))
1328
1404
  names.push(principal.displayName);
1329
1405
  }
@@ -1479,7 +1555,7 @@ export class AccessControlService {
1479
1555
  */
1480
1556
  async previewAccessAfterRelocation(workspaceId, userEmail, fromPath, toPath, opts) {
1481
1557
  const repoDir = await this.repoDir(workspaceId);
1482
- const { isFolder, ownThere: own, landing } = await this.relocationSubject(repoDir, fromPath, toPath);
1558
+ const { isFolder, ownThere: own, landing } = await this.relocationSubject(repoDir, fromPath, toPath, opts?.sourceContent);
1483
1559
  const model = withRelocatedAccessFiles(await this.loadModel(workspaceId), fromPath, toPath, { sourceRemains: opts?.sourceRemains === true, sourceIsFolder: isFolder, landing });
1484
1560
  const machineOwned = this.machineOwnedWriteRule(userEmail, toPath);
1485
1561
  return {
@@ -1604,9 +1680,9 @@ export class AccessControlService {
1604
1680
  }
1605
1681
  // Roles = the built-in `everyone` role plus every declared role's display
1606
1682
  // name — ROLE principals only, never groups. Each role is enumerated via
1607
- // its `role/<canonical>` alias key, which exists exactly once per role
1608
- // (the bare key may be owned by a same-named group under group-first
1609
- // precedence, and merged group entries must not appear here). `everyone`
1683
+ // its `role/<canonical>` alias key, which exists exactly once per role;
1684
+ // each group likewise via its `group/<canonical>` alias (a group that
1685
+ // owns its bare key is registered under both, as one record). `everyone`
1610
1686
  // is surfaced so the share UI can grant public read; the grant route
1611
1687
  // gates it to the `read` verb only (write/owner/download everyone stay a
1612
1688
  // direct-access.md edit).
@@ -1620,7 +1696,7 @@ export class AccessControlService {
1620
1696
  for (const [key, principal] of model.roles.byCanonical) {
1621
1697
  if (key.startsWith(ROLE_TOKEN_PREFIX))
1622
1698
  roles.push(principal.displayName);
1623
- else if (principal.kind === 'group')
1699
+ else if (key.startsWith(GROUP_TOKEN_PREFIX) && principal.kind === 'group')
1624
1700
  groups.push(principal.displayName);
1625
1701
  else if (principal.kind === 'plugin' &&
1626
1702
  principal.pluginName &&
@@ -1990,13 +2066,18 @@ export class AccessControlService {
1990
2066
  * in the new (body-governed) format, where its frontmatter is self-access —
1991
2067
  * see {@link accessMdSelfEntries}. A legacy `access.md` still yields null:
1992
2068
  * its frontmatter IS the folder rules the dir chain already applies.
2069
+ *
2070
+ * Any other file declares them in its frontmatter alone, so only as much of
2071
+ * it is read as the frontmatter takes (see {@link frontmatterHead}): a batch
2072
+ * over a tree of megabyte transcripts reads a line of each, not the tree.
1993
2073
  */
1994
2074
  async readOwnEntries(repoDir, relativePath) {
1995
2075
  if (relativePath === 'roles.yaml')
1996
2076
  return null;
2077
+ const file = path.join(repoDir, relativePath);
1997
2078
  let text;
1998
2079
  try {
1999
- text = await fs.readFile(path.join(repoDir, relativePath), 'utf-8');
2080
+ text = isAccessMdPath(relativePath) ? await fs.readFile(file, 'utf-8') : await frontmatterHead(file);
2000
2081
  }
2001
2082
  catch {
2002
2083
  return null;
@@ -2028,18 +2109,26 @@ export class AccessControlService {
2028
2109
  * Only absence is an answer (see `isAbsence`): a probe that failed on
2029
2110
  * permissions or I/O does not say "this is a file", and reading a folder as
2030
2111
  * one would resolve it through frontmatter it does not have.
2112
+ *
2113
+ * `sourceContent` is the text of a source that is NO repository path — a
2114
+ * file staged in the workspace's `tmp/` folder, which `copy_file` lands in
2115
+ * the repository. Nothing is probed on disk for it: it is one file by the
2116
+ * ref's own rule, and its text is read exactly as a repository file's is,
2117
+ * so the copy previews the rules it carries in.
2031
2118
  */
2032
- async relocationSubject(repoDir, fromPath, toPath) {
2119
+ async relocationSubject(repoDir, fromPath, toPath, sourceContent) {
2033
2120
  const nothing = { isFolder: false, ownHere: null, ownThere: null, landing: null };
2034
- const fromStat = await fs.lstat(path.join(repoDir, fromPath)).catch((err) => {
2035
- if (isAbsence(err))
2036
- return null;
2037
- throw err;
2038
- });
2039
- if (fromStat?.isSymbolicLink() === true)
2040
- return nothing;
2041
- if (fromStat?.isDirectory() === true)
2042
- return { ...nothing, isFolder: true };
2121
+ if (sourceContent === undefined) {
2122
+ const fromStat = await fs.lstat(path.join(repoDir, fromPath)).catch((err) => {
2123
+ if (isAbsence(err))
2124
+ return null;
2125
+ throw err;
2126
+ });
2127
+ if (fromStat?.isSymbolicLink() === true)
2128
+ return nothing;
2129
+ if (fromStat?.isDirectory() === true)
2130
+ return { ...nothing, isFolder: true };
2131
+ }
2043
2132
  // `roles.yaml` carries no own-entries wherever it sits, exactly as
2044
2133
  // `readOwnEntries` has it; a read that fails for any reason is no rules,
2045
2134
  // which is what the gates would say about the same file.
@@ -2047,7 +2136,7 @@ export class AccessControlService {
2047
2136
  return nothing;
2048
2137
  let text;
2049
2138
  try {
2050
- text = await fs.readFile(path.join(repoDir, fromPath), 'utf-8');
2139
+ text = sourceContent ?? (await fs.readFile(path.join(repoDir, fromPath), 'utf-8'));
2051
2140
  }
2052
2141
  catch {
2053
2142
  return nothing;