@bevel-software/platform-core-backend 0.35.0 → 0.37.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 (851) hide show
  1. package/agent-guide/directory-structure.md +1 -1
  2. package/agent-guide/onboarding.md +111 -0
  3. package/agent-guide/start-here.md +1 -0
  4. package/agent-guide/tool-manuals.md +1 -0
  5. package/dist/core/catalog-cache-invalidation.d.ts +2 -2
  6. package/dist/core/catalog-cache-invalidation.d.ts.map +1 -1
  7. package/dist/core/compose-embed.d.ts +2 -2
  8. package/dist/core/compose-embed.d.ts.map +1 -1
  9. package/dist/core/compose-embed.js +1 -1
  10. package/dist/core/compose-embed.js.map +1 -1
  11. package/dist/core/core-ports.d.ts +19 -4
  12. package/dist/core/core-ports.d.ts.map +1 -1
  13. package/dist/core/core-ports.js.map +1 -1
  14. package/dist/core/create-core-server.d.ts.map +1 -1
  15. package/dist/core/create-core-server.js +26 -24
  16. package/dist/core/create-core-server.js.map +1 -1
  17. package/dist/core/create-core-services.d.ts +19 -13
  18. package/dist/core/create-core-services.d.ts.map +1 -1
  19. package/dist/core/create-core-services.js +72 -57
  20. package/dist/core/create-core-services.js.map +1 -1
  21. package/dist/core/lifecycle.d.ts +4 -4
  22. package/dist/core/lifecycle.d.ts.map +1 -1
  23. package/dist/core/lifecycle.js +5 -5
  24. package/dist/core/lifecycle.js.map +1 -1
  25. package/dist/core-config.d.ts +2 -2
  26. package/dist/core-config.js +3 -3
  27. package/dist/core-config.js.map +1 -1
  28. package/dist/index.d.ts +4 -4
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +3 -3
  31. package/dist/index.js.map +1 -1
  32. package/dist/modules/access/access-control.service.js +1 -1
  33. package/dist/modules/access/access-control.service.js.map +1 -1
  34. package/dist/modules/access/access-declarations.d.ts +2 -2
  35. package/dist/modules/access/access-declarations.d.ts.map +1 -1
  36. package/dist/modules/access/access-declarations.js +2 -2
  37. package/dist/modules/access/access-declarations.js.map +1 -1
  38. package/dist/modules/access/access-mutation.service.d.ts +2 -2
  39. package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
  40. package/dist/modules/access/access-mutation.service.js +2 -2
  41. package/dist/modules/access/access-mutation.service.js.map +1 -1
  42. package/dist/modules/access/access-requests.routes.d.ts +2 -2
  43. package/dist/modules/access/access-requests.routes.d.ts.map +1 -1
  44. package/dist/modules/access/access-requests.routes.js +5 -5
  45. package/dist/modules/access/access-requests.routes.js.map +1 -1
  46. package/dist/modules/access/access-requests.service.d.ts +2 -2
  47. package/dist/modules/access/access-requests.service.d.ts.map +1 -1
  48. package/dist/modules/access/access-requests.service.js +11 -11
  49. package/dist/modules/access/access-requests.service.js.map +1 -1
  50. package/dist/modules/access/access.routes.d.ts +2 -2
  51. package/dist/modules/access/access.routes.d.ts.map +1 -1
  52. package/dist/modules/access/access.routes.js +9 -9
  53. package/dist/modules/access/access.routes.js.map +1 -1
  54. package/dist/modules/access/admin-locked-commit.d.ts +5 -5
  55. package/dist/modules/access/admin-locked-commit.d.ts.map +1 -1
  56. package/dist/modules/access/admin-locked-commit.js +7 -7
  57. package/dist/modules/access/admin-locked-commit.js.map +1 -1
  58. package/dist/modules/access/admin-route-helpers.d.ts +1 -1
  59. package/dist/modules/access/admin-route-helpers.js +5 -5
  60. package/dist/modules/access/admin-route-helpers.js.map +1 -1
  61. package/dist/modules/access/change-read-gate.d.ts +1 -1
  62. package/dist/modules/access/change-read-gate.js +1 -1
  63. package/dist/modules/access/groups-admin.service.d.ts +7 -7
  64. package/dist/modules/access/groups-admin.service.d.ts.map +1 -1
  65. package/dist/modules/access/groups-admin.service.js +6 -6
  66. package/dist/modules/access/groups-admin.service.js.map +1 -1
  67. package/dist/modules/access/reference-scan.d.ts +1 -1
  68. package/dist/modules/access/roles-admin.service.d.ts +7 -7
  69. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  70. package/dist/modules/access/roles-admin.service.js +8 -8
  71. package/dist/modules/access/roles-admin.service.js.map +1 -1
  72. package/dist/modules/access/synced-groups-committer.d.ts +4 -4
  73. package/dist/modules/access/synced-groups-committer.d.ts.map +1 -1
  74. package/dist/modules/access/synced-groups-committer.js +7 -7
  75. package/dist/modules/access/synced-groups-committer.js.map +1 -1
  76. package/dist/modules/access/user-access-removal.service.d.ts +6 -6
  77. package/dist/modules/access/user-access-removal.service.d.ts.map +1 -1
  78. package/dist/modules/access/user-access-removal.service.js +4 -4
  79. package/dist/modules/access/user-access-removal.service.js.map +1 -1
  80. package/dist/modules/access-model/access-errors.d.ts +4 -4
  81. package/dist/modules/access-model/access-errors.d.ts.map +1 -1
  82. package/dist/modules/access-model/access-errors.js +4 -4
  83. package/dist/modules/access-model/access-errors.js.map +1 -1
  84. package/dist/modules/access-model/access-grammar.d.ts +1 -1
  85. package/dist/modules/access-model/access-grammar.js +4 -4
  86. package/dist/modules/access-model/change-gate.d.ts +1 -1
  87. package/dist/modules/access-model/change-gate.js +1 -1
  88. package/dist/modules/access-model/roles-yaml-guard.d.ts +4 -4
  89. package/dist/modules/access-model/roles-yaml-guard.d.ts.map +1 -1
  90. package/dist/modules/access-model/roles-yaml-guard.js +4 -4
  91. package/dist/modules/access-model/roles-yaml-guard.js.map +1 -1
  92. package/dist/modules/agent-guide/agent-guide.d.ts +19 -14
  93. package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
  94. package/dist/modules/agent-guide/agent-guide.js +42 -18
  95. package/dist/modules/agent-guide/agent-guide.js.map +1 -1
  96. package/dist/modules/agent-guide/guide-budget.d.ts +5 -2
  97. package/dist/modules/agent-guide/guide-budget.d.ts.map +1 -1
  98. package/dist/modules/agent-guide/guide-budget.js +6 -2
  99. package/dist/modules/agent-guide/guide-budget.js.map +1 -1
  100. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +12 -5
  101. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -1
  102. package/dist/modules/agent-instructions/agent-instructions.routes.js +13 -6
  103. package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -1
  104. package/dist/modules/agent-instructions/compose.d.ts +15 -8
  105. package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
  106. package/dist/modules/agent-instructions/compose.js +24 -10
  107. package/dist/modules/agent-instructions/compose.js.map +1 -1
  108. package/dist/modules/agent-instructions/shared-file-rules.d.ts +14 -6
  109. package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
  110. package/dist/modules/agent-instructions/shared-file-rules.js +51 -11
  111. package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
  112. package/dist/modules/auth/account-erasure.service.d.ts +36 -7
  113. package/dist/modules/auth/account-erasure.service.d.ts.map +1 -1
  114. package/dist/modules/auth/account-erasure.service.js +30 -10
  115. package/dist/modules/auth/account-erasure.service.js.map +1 -1
  116. package/dist/modules/auth/account-hooks.d.ts +20 -0
  117. package/dist/modules/auth/account-hooks.d.ts.map +1 -1
  118. package/dist/modules/auth/account-hooks.js +10 -0
  119. package/dist/modules/auth/account-hooks.js.map +1 -1
  120. package/dist/modules/auth/account-switch-lock.js +2 -2
  121. package/dist/modules/auth/account-switch-lock.js.map +1 -1
  122. package/dist/modules/auth/account.routes.d.ts +7 -2
  123. package/dist/modules/auth/account.routes.d.ts.map +1 -1
  124. package/dist/modules/auth/account.routes.js +23 -4
  125. package/dist/modules/auth/account.routes.js.map +1 -1
  126. package/dist/modules/auth/auth.service.d.ts +1 -1
  127. package/dist/modules/auth/auth.service.js +4 -4
  128. package/dist/modules/auth/auth.service.js.map +1 -1
  129. package/dist/modules/auth/index.d.ts +1 -0
  130. package/dist/modules/auth/index.d.ts.map +1 -1
  131. package/dist/modules/database/connection.d.ts.map +1 -1
  132. package/dist/modules/database/connection.js +11 -0
  133. package/dist/modules/database/connection.js.map +1 -1
  134. package/dist/modules/database/core-schema.js +2 -2
  135. package/dist/modules/database/core-schema.js.map +1 -1
  136. package/dist/modules/diff/diff.routes.d.ts +3 -3
  137. package/dist/modules/diff/diff.routes.d.ts.map +1 -1
  138. package/dist/modules/diff/diff.routes.js +7 -7
  139. package/dist/modules/diff/diff.routes.js.map +1 -1
  140. package/dist/modules/embed/embed.routes.js +1 -1
  141. package/dist/modules/embed/embed.routes.js.map +1 -1
  142. package/dist/modules/embed/embed.service.d.ts +4 -4
  143. package/dist/modules/embed/embed.service.d.ts.map +1 -1
  144. package/dist/modules/embed/embed.service.js +18 -18
  145. package/dist/modules/embed/embed.service.js.map +1 -1
  146. package/dist/modules/kb-fs/branch-name.js +8 -8
  147. package/dist/modules/kb-fs/branch-name.js.map +1 -1
  148. package/dist/modules/kb-fs/file-change-notifier.d.ts +51 -26
  149. package/dist/modules/kb-fs/file-change-notifier.d.ts.map +1 -1
  150. package/dist/modules/kb-fs/file-change-notifier.js +6 -8
  151. package/dist/modules/kb-fs/file-change-notifier.js.map +1 -1
  152. package/dist/modules/kb-fs/locking-filesystem.d.ts +9 -16
  153. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
  154. package/dist/modules/kb-fs/locking-filesystem.js +31 -39
  155. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
  156. package/dist/modules/kb-fs/repo-path.js +5 -5
  157. package/dist/modules/kb-fs/repo-path.js.map +1 -1
  158. package/dist/modules/kb-fs/tmp-ref.d.ts +2 -2
  159. package/dist/modules/kb-fs/tmp-ref.d.ts.map +1 -1
  160. package/dist/modules/kb-fs/tmp-ref.js +2 -2
  161. package/dist/modules/kb-fs/tmp-ref.js.map +1 -1
  162. package/dist/modules/kb-sync/index.d.ts +1 -1
  163. package/dist/modules/kb-sync/index.d.ts.map +1 -1
  164. package/dist/modules/kb-sync/kb-sync.interface.d.ts +3 -3
  165. package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -1
  166. package/dist/modules/kb-sync/kb-sync.service.d.ts +3 -3
  167. package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
  168. package/dist/modules/kb-sync/kb-sync.service.js +10 -10
  169. package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
  170. package/dist/modules/mcp/mcp.service.d.ts +11 -4
  171. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  172. package/dist/modules/mcp/mcp.service.js +75 -19
  173. package/dist/modules/mcp/mcp.service.js.map +1 -1
  174. package/dist/modules/onboarding/onboarding.contract.d.ts +1 -1
  175. package/dist/modules/onboarding/onboarding.routes.js +2 -2
  176. package/dist/modules/onboarding/onboarding.routes.js.map +1 -1
  177. package/dist/modules/onboarding/starter-pack.service.d.ts +5 -7
  178. package/dist/modules/onboarding/starter-pack.service.d.ts.map +1 -1
  179. package/dist/modules/onboarding/starter-pack.service.js +4 -4
  180. package/dist/modules/onboarding/starter-pack.service.js.map +1 -1
  181. package/dist/modules/plugins/join-request-jobs.service.d.ts +2 -2
  182. package/dist/modules/plugins/join-request-jobs.service.d.ts.map +1 -1
  183. package/dist/modules/plugins/join-request-jobs.service.js +7 -7
  184. package/dist/modules/plugins/join-request-jobs.service.js.map +1 -1
  185. package/dist/modules/plugins/join-requests.service.d.ts +3 -3
  186. package/dist/modules/plugins/join-requests.service.d.ts.map +1 -1
  187. package/dist/modules/plugins/join-requests.service.js +5 -5
  188. package/dist/modules/plugins/join-requests.service.js.map +1 -1
  189. package/dist/modules/plugins/plugin-rename.service.js +1 -1
  190. package/dist/modules/plugins/plugin-rename.service.js.map +1 -1
  191. package/dist/modules/plugins/plugins.routes.d.ts +2 -2
  192. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  193. package/dist/modules/plugins/plugins.routes.js +8 -8
  194. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  195. package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +77 -2
  196. package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
  197. package/dist/modules/secrets-vault/db-secrets-vault.service.js +479 -53
  198. package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
  199. package/dist/modules/secrets-vault/index.d.ts +2 -1
  200. package/dist/modules/secrets-vault/index.d.ts.map +1 -1
  201. package/dist/modules/secrets-vault/index.js +1 -0
  202. package/dist/modules/secrets-vault/index.js.map +1 -1
  203. package/dist/modules/secrets-vault/managed-sign-in-erasure.d.ts +36 -0
  204. package/dist/modules/secrets-vault/managed-sign-in-erasure.d.ts.map +1 -0
  205. package/dist/modules/secrets-vault/managed-sign-in-erasure.js +37 -0
  206. package/dist/modules/secrets-vault/managed-sign-in-erasure.js.map +1 -0
  207. package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +152 -0
  208. package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
  209. package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
  210. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +7 -1
  211. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
  212. package/dist/modules/secrets-vault/secrets-vault.routes.js +125 -10
  213. package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
  214. package/dist/modules/settings/connection-check.js +1 -1
  215. package/dist/modules/settings/connection-check.js.map +1 -1
  216. package/dist/modules/settings/deployment-settings.service.js +1 -1
  217. package/dist/modules/settings/git-root-folders.js +1 -1
  218. package/dist/modules/settings/git-root-folders.js.map +1 -1
  219. package/dist/modules/settings/setup.routes.d.ts +1 -1
  220. package/dist/modules/settings/setup.routes.js +1 -1
  221. package/dist/modules/skills/pending-skills.service.d.ts +3 -3
  222. package/dist/modules/skills/pending-skills.service.d.ts.map +1 -1
  223. package/dist/modules/skills/pending-skills.service.js +4 -4
  224. package/dist/modules/skills/pending-skills.service.js.map +1 -1
  225. package/dist/modules/skills/skill-access-requests.routes.d.ts +2 -2
  226. package/dist/modules/skills/skill-access-requests.routes.d.ts.map +1 -1
  227. package/dist/modules/skills/skill-access-requests.routes.js +5 -5
  228. package/dist/modules/skills/skill-access-requests.routes.js.map +1 -1
  229. package/dist/modules/tool-helpers/tool-context.d.ts +4 -4
  230. package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
  231. package/dist/modules/tool-helpers/tool-context.js +2 -2
  232. package/dist/modules/tool-helpers/tool-context.js.map +1 -1
  233. package/dist/modules/tool-helpers/tool-handler.js +2 -2
  234. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  235. package/dist/modules/tool-helpers/tool.contract.d.ts +4 -4
  236. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  237. package/dist/modules/tool-manuals/mcp-server-edit.service.js +1 -1
  238. package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -1
  239. package/dist/modules/tool-manuals/pending-tools.service.d.ts +3 -3
  240. package/dist/modules/tool-manuals/pending-tools.service.d.ts.map +1 -1
  241. package/dist/modules/tool-manuals/pending-tools.service.js +4 -4
  242. package/dist/modules/tool-manuals/pending-tools.service.js.map +1 -1
  243. package/dist/modules/tool-manuals/tool-delete.service.js +1 -1
  244. package/dist/modules/tool-manuals/tool-delete.service.js.map +1 -1
  245. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +43 -3
  246. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  247. package/dist/modules/tool-manuals/tool-manuals.service.d.ts +6 -0
  248. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  249. package/dist/modules/tool-manuals/tool-manuals.service.js +51 -5
  250. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  251. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +3 -0
  252. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  253. package/dist/modules/tool-manuals/tool-manuals.tools.js +36 -15
  254. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  255. package/dist/modules/version-control/agent-tools/change-request-read-shape.d.ts.map +1 -0
  256. package/dist/modules/version-control/agent-tools/change-request-read-shape.js.map +1 -0
  257. package/dist/modules/version-control/agent-tools/change-request-read.tools.d.ts.map +1 -0
  258. package/dist/modules/{workflow → version-control}/agent-tools/change-request-read.tools.js +2 -2
  259. package/dist/modules/version-control/agent-tools/change-request-read.tools.js.map +1 -0
  260. package/dist/modules/version-control/agent-tools/change-request-summary.d.ts.map +1 -0
  261. package/dist/modules/version-control/agent-tools/change-request-summary.js.map +1 -0
  262. package/dist/modules/{workflow/agent-tools/workflow.tools.d.ts → version-control/agent-tools/version-control.tools.d.ts} +4 -4
  263. package/dist/modules/version-control/agent-tools/version-control.tools.d.ts.map +1 -0
  264. package/dist/modules/{workflow/agent-tools/workflow.tools.js → version-control/agent-tools/version-control.tools.js} +31 -31
  265. package/dist/modules/version-control/agent-tools/version-control.tools.js.map +1 -0
  266. package/dist/modules/{workflow → version-control}/change-request-hooks.d.ts +1 -1
  267. package/dist/modules/version-control/change-request-hooks.d.ts.map +1 -0
  268. package/dist/modules/version-control/change-request-hooks.js.map +1 -0
  269. package/dist/modules/version-control/change-request-review/approval-lock.d.ts.map +1 -0
  270. package/dist/modules/version-control/change-request-review/approval-lock.js.map +1 -0
  271. package/dist/modules/{workflow/review-workflow/review-workflow.interface.d.ts → version-control/change-request-review/change-request-review.interface.d.ts} +2 -2
  272. package/dist/modules/version-control/change-request-review/change-request-review.interface.d.ts.map +1 -0
  273. package/dist/modules/version-control/change-request-review/change-request-review.interface.js +2 -0
  274. package/dist/modules/version-control/change-request-review/change-request-review.interface.js.map +1 -0
  275. package/dist/modules/{workflow/review-workflow/review-workflow.service.d.ts → version-control/change-request-review/change-request-review.service.d.ts} +3 -3
  276. package/dist/modules/version-control/change-request-review/change-request-review.service.d.ts.map +1 -0
  277. package/dist/modules/{workflow/review-workflow/review-workflow.service.js → version-control/change-request-review/change-request-review.service.js} +26 -26
  278. package/dist/modules/version-control/change-request-review/change-request-review.service.js.map +1 -0
  279. package/dist/modules/{workflow → version-control}/event-bus.d.ts +8 -8
  280. package/dist/modules/version-control/event-bus.d.ts.map +1 -0
  281. package/dist/modules/{workflow → version-control}/event-bus.js +4 -4
  282. package/dist/modules/version-control/event-bus.js.map +1 -0
  283. package/dist/modules/{workflow → version-control}/events-delivery.d.ts +4 -4
  284. package/dist/modules/version-control/events-delivery.d.ts.map +1 -0
  285. package/dist/modules/version-control/events-delivery.js.map +1 -0
  286. package/dist/modules/{workflow → version-control}/events.routes.d.ts +4 -4
  287. package/dist/modules/version-control/events.routes.d.ts.map +1 -0
  288. package/dist/modules/version-control/events.routes.js.map +1 -0
  289. package/dist/modules/{workflow → version-control}/file-lock.service.d.ts +2 -2
  290. package/dist/modules/version-control/file-lock.service.d.ts.map +1 -0
  291. package/dist/modules/{workflow → version-control}/file-lock.service.js +6 -6
  292. package/dist/modules/version-control/file-lock.service.js.map +1 -0
  293. package/dist/modules/version-control/git/change-request-link.d.ts.map +1 -0
  294. package/dist/modules/version-control/git/change-request-link.js.map +1 -0
  295. package/dist/modules/version-control/git/git-version.d.ts.map +1 -0
  296. package/dist/modules/version-control/git/git-version.js.map +1 -0
  297. package/dist/modules/{workflow → version-control}/git/git.service.d.ts +26 -8
  298. package/dist/modules/version-control/git/git.service.d.ts.map +1 -0
  299. package/dist/modules/{workflow → version-control}/git/git.service.js +106 -55
  300. package/dist/modules/version-control/git/git.service.js.map +1 -0
  301. package/dist/modules/version-control/git/merge-commit.d.ts.map +1 -0
  302. package/dist/modules/version-control/git/merge-commit.js.map +1 -0
  303. package/dist/modules/version-control/git/node-git-runner.d.ts.map +1 -0
  304. package/dist/modules/{workflow → version-control}/git/node-git-runner.js +1 -1
  305. package/dist/modules/version-control/git/node-git-runner.js.map +1 -0
  306. package/dist/modules/{workflow → version-control}/git/pull-request.service.d.ts +5 -5
  307. package/dist/modules/version-control/git/pull-request.service.d.ts.map +1 -0
  308. package/dist/modules/{workflow → version-control}/git/pull-request.service.js +10 -10
  309. package/dist/modules/version-control/git/pull-request.service.js.map +1 -0
  310. package/dist/modules/{workflow → version-control}/index.d.ts +6 -5
  311. package/dist/modules/version-control/index.d.ts.map +1 -0
  312. package/dist/modules/{workflow → version-control}/index.js +3 -2
  313. package/dist/modules/version-control/index.js.map +1 -0
  314. package/dist/modules/{workflow → version-control}/pending-commits.service.d.ts +2 -2
  315. package/dist/modules/version-control/pending-commits.service.d.ts.map +1 -0
  316. package/dist/modules/{workflow → version-control}/pending-commits.service.js +2 -2
  317. package/dist/modules/version-control/pending-commits.service.js.map +1 -0
  318. package/dist/modules/{workflow → version-control}/pending-commits.worker.d.ts +5 -5
  319. package/dist/modules/version-control/pending-commits.worker.d.ts.map +1 -0
  320. package/dist/modules/{workflow → version-control}/pending-commits.worker.js +3 -3
  321. package/dist/modules/version-control/pending-commits.worker.js.map +1 -0
  322. package/dist/modules/{workflow → version-control}/read-on-branch.d.ts +2 -2
  323. package/dist/modules/version-control/read-on-branch.d.ts.map +1 -0
  324. package/dist/modules/{workflow → version-control}/read-on-branch.js +2 -2
  325. package/dist/modules/version-control/read-on-branch.js.map +1 -0
  326. package/dist/modules/version-control/recovery-bot.d.ts.map +1 -0
  327. package/dist/modules/version-control/recovery-bot.js.map +1 -0
  328. package/dist/modules/version-control/sanitize-error.d.ts.map +1 -0
  329. package/dist/modules/version-control/sanitize-error.js.map +1 -0
  330. package/dist/modules/{workflow/workflow-hooks.d.ts → version-control/version-control-hooks.d.ts} +33 -6
  331. package/dist/modules/version-control/version-control-hooks.d.ts.map +1 -0
  332. package/dist/modules/{workflow/workflow-hooks.js → version-control/version-control-hooks.js} +20 -3
  333. package/dist/modules/version-control/version-control-hooks.js.map +1 -0
  334. package/dist/modules/{workflow/workflow.routes.d.ts → version-control/version-control.routes.d.ts} +6 -5
  335. package/dist/modules/version-control/version-control.routes.d.ts.map +1 -0
  336. package/dist/modules/{workflow/workflow.routes.js → version-control/version-control.routes.js} +61 -60
  337. package/dist/modules/version-control/version-control.routes.js.map +1 -0
  338. package/dist/modules/{workflow/workflow.service.d.ts → version-control/version-control.service.d.ts} +26 -46
  339. package/dist/modules/version-control/version-control.service.d.ts.map +1 -0
  340. package/dist/modules/{workflow/workflow.service.js → version-control/version-control.service.js} +93 -107
  341. package/dist/modules/version-control/version-control.service.js.map +1 -0
  342. package/dist/modules/workspace/agent-access.gate.d.ts +5 -5
  343. package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -1
  344. package/dist/modules/workspace/agent-access.gate.js +1 -1
  345. package/dist/modules/workspace/git-internals.middleware.d.ts +1 -1
  346. package/dist/modules/workspace/git-internals.middleware.js +2 -2
  347. package/dist/modules/workspace/kb-repository.js +1 -1
  348. package/dist/modules/workspace/kb-repository.js.map +1 -1
  349. package/dist/modules/workspace/startup/kb-git.d.ts +1 -1
  350. package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -1
  351. package/dist/modules/workspace/startup/kb-git.js +1 -1
  352. package/dist/modules/workspace/startup/kb-git.js.map +1 -1
  353. package/dist/modules/workspace/startup/steps/seed-tree.d.ts +2 -2
  354. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
  355. package/dist/modules/workspace/startup/steps/seed-tree.js +1 -1
  356. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
  357. package/dist/modules/workspace/startup/steps/template-files.step.d.ts +2 -2
  358. package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
  359. package/dist/modules/workspace/startup/steps/template-files.step.js +2 -2
  360. package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
  361. package/dist/modules/workspace/startup/steps/template-source.d.ts +17 -4
  362. package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -1
  363. package/dist/modules/workspace/startup/steps/template-source.js +8 -5
  364. package/dist/modules/workspace/startup/steps/template-source.js.map +1 -1
  365. package/dist/modules/workspace/workspace-tmp.js +3 -3
  366. package/dist/modules/workspace/workspace-tmp.js.map +1 -1
  367. package/dist/modules/workspace/workspace.routes.d.ts +4 -4
  368. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  369. package/dist/modules/workspace/workspace.routes.js +22 -22
  370. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  371. package/dist/modules/workspace/workspace.service.d.ts +6 -6
  372. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  373. package/dist/modules/workspace/workspace.service.js +11 -11
  374. package/dist/modules/workspace/workspace.service.js.map +1 -1
  375. package/dist/modules/workspace/workspace.tools.d.ts +2 -1
  376. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  377. package/dist/modules/workspace/workspace.tools.js +15 -10
  378. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  379. package/dist/shared/canonical-file-identity.d.ts +2 -2
  380. package/dist/shared/canonical-file-identity.js +5 -5
  381. package/dist/shared/canonical-file-identity.js.map +1 -1
  382. package/dist/shared/domain-errors.d.ts +39 -39
  383. package/dist/shared/domain-errors.d.ts.map +1 -1
  384. package/dist/shared/domain-errors.js +44 -44
  385. package/dist/shared/domain-errors.js.map +1 -1
  386. package/dist/shared/fire-and-forget.d.ts +14 -0
  387. package/dist/shared/fire-and-forget.d.ts.map +1 -0
  388. package/dist/shared/fire-and-forget.js +23 -0
  389. package/dist/shared/fire-and-forget.js.map +1 -0
  390. package/dist/shared/git.contract.d.ts +1 -1
  391. package/dist/shared/git.contract.js +1 -1
  392. package/dist/shared/http-errors.d.ts +3 -3
  393. package/dist/shared/http-errors.d.ts.map +1 -1
  394. package/dist/shared/http-errors.js +1 -1
  395. package/dist/shared/http-errors.js.map +1 -1
  396. package/dist/shared/pending-proposals.d.ts +3 -3
  397. package/dist/shared/pending-proposals.d.ts.map +1 -1
  398. package/dist/shared/pending-proposals.js +2 -2
  399. package/dist/shared/pending-proposals.js.map +1 -1
  400. package/dist/tenancy/static-tenant-source.js +1 -1
  401. package/dist/tenancy/static-tenant-source.js.map +1 -1
  402. package/migrations/0012_agent_audit.sql +40 -40
  403. package/migrations/0013_agent_identity_and_key_soft_delete.sql +29 -29
  404. package/migrations/meta/0012_snapshot.json +2246 -2246
  405. package/migrations/meta/0013_snapshot.json +2258 -2258
  406. package/package.json +4 -4
  407. package/src/__tests__/account-admin.ts +27 -0
  408. package/src/__tests__/capturing-logger.ts +36 -0
  409. package/src/__tests__/hexis-catalog.ts +4 -4
  410. package/src/__tests__/kb-context.ts +30 -30
  411. package/src/__tests__/kb-layout-config.test.ts +21 -0
  412. package/src/__tests__/open-change-gate.ts +1 -1
  413. package/src/__tests__/real-access.ts +16 -0
  414. package/src/__tests__/real-auth.ts +13 -0
  415. package/src/__tests__/real-readers.ts +27 -0
  416. package/src/__tests__/real-version-control.ts +22 -0
  417. package/src/__tests__/tool-handler.ts +15 -0
  418. package/src/__tests__/tool-wiring.ts +10 -4
  419. package/src/__tests__/workspace-service.ts +1 -1
  420. package/src/core/__tests__/catalog-cache-invalidation.test.ts +3 -3
  421. package/src/core/__tests__/commit-announcements-wired-once.test.ts +53 -0
  422. package/src/core/__tests__/compose-embed.test.ts +1 -1
  423. package/src/core/__tests__/damaged-document.tools.test.ts +3 -3
  424. package/src/core/__tests__/erasure-slices-wired-once.test.ts +46 -0
  425. package/src/core/__tests__/gated-boot.test.ts +2 -2
  426. package/src/core/__tests__/lifecycle.test.ts +9 -9
  427. package/src/core/catalog-cache-invalidation.ts +2 -2
  428. package/src/core/compose-embed.ts +3 -3
  429. package/src/core/core-ports.ts +19 -4
  430. package/src/core/create-core-server.ts +39 -25
  431. package/src/core/create-core-services.ts +83 -65
  432. package/src/core/lifecycle.ts +9 -9
  433. package/src/core-config.ts +3 -3
  434. package/src/index.ts +5 -5
  435. package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +4 -4
  436. package/src/modules/access/__tests__/access-own-read-grant.test.ts +14 -14
  437. package/src/modules/access/__tests__/access-requests.routes.test.ts +23 -23
  438. package/src/modules/access/__tests__/access.routes.admins.test.ts +4 -4
  439. package/src/modules/access/__tests__/access.routes.batch.test.ts +4 -4
  440. package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +6 -6
  441. package/src/modules/access/__tests__/access.routes.group-grant.test.ts +6 -6
  442. package/src/modules/access/__tests__/access.routes.overrides.test.ts +5 -5
  443. package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +6 -6
  444. package/src/modules/access/__tests__/access.routes.prospective.test.ts +3 -3
  445. package/src/modules/access/__tests__/access.routes.revoke.test.ts +6 -6
  446. package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +6 -6
  447. package/src/modules/access/__tests__/admin-locked-commit.test.ts +6 -6
  448. package/src/modules/access/__tests__/admin-route-helpers.test.ts +5 -5
  449. package/src/modules/access/__tests__/groups-admin.service.test.ts +44 -44
  450. package/src/modules/access/__tests__/roles-admin.security.test.ts +14 -13
  451. package/src/modules/access/__tests__/roles-admin.service.test.ts +34 -34
  452. package/src/modules/access/__tests__/roles-capabilities.test.ts +29 -29
  453. package/src/modules/access/__tests__/roles.routes.test.ts +11 -11
  454. package/src/modules/access/__tests__/synced-groups-committer.test.ts +14 -14
  455. package/src/modules/access/__tests__/user-access-removal.service.test.ts +33 -33
  456. package/src/modules/access/access-control.service.ts +1 -1
  457. package/src/modules/access/access-declarations.ts +2 -2
  458. package/src/modules/access/access-mutation.service.ts +2 -2
  459. package/src/modules/access/access-requests.routes.ts +7 -7
  460. package/src/modules/access/access-requests.service.ts +13 -13
  461. package/src/modules/access/access.routes.ts +11 -11
  462. package/src/modules/access/admin-locked-commit.ts +11 -11
  463. package/src/modules/access/admin-route-helpers.ts +5 -5
  464. package/src/modules/access/change-read-gate.ts +1 -1
  465. package/src/modules/access/groups-admin.service.ts +7 -7
  466. package/src/modules/access/reference-scan.ts +1 -1
  467. package/src/modules/access/roles-admin.service.ts +9 -9
  468. package/src/modules/access/synced-groups-committer.ts +11 -11
  469. package/src/modules/access/user-access-removal.service.ts +7 -7
  470. package/src/modules/access-model/__tests__/access-grammar.test.ts +2 -2
  471. package/src/modules/access-model/access-errors.ts +4 -4
  472. package/src/modules/access-model/access-grammar.ts +4 -4
  473. package/src/modules/access-model/change-gate.ts +1 -1
  474. package/src/modules/access-model/roles-yaml-guard.ts +4 -4
  475. package/src/modules/agent-guide/__tests__/agent-guide.test.ts +19 -4
  476. package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +1 -1
  477. package/src/modules/agent-guide/__tests__/guide-matches-code.test.ts +2 -2
  478. package/src/modules/agent-guide/agent-guide.ts +48 -22
  479. package/src/modules/agent-guide/guide-budget.ts +6 -2
  480. package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +4 -5
  481. package/src/modules/agent-instructions/__tests__/compose.test.ts +1 -1
  482. package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +113 -7
  483. package/src/modules/agent-instructions/agent-instructions.routes.ts +12 -5
  484. package/src/modules/agent-instructions/compose.ts +24 -9
  485. package/src/modules/agent-instructions/shared-file-rules.ts +53 -11
  486. package/src/modules/agent-scope/__tests__/agent-surfaces.connect-as.test.ts +1 -1
  487. package/src/modules/audit/__tests__/agent-rest-audit.middleware.test.ts +113 -113
  488. package/src/modules/audit/__tests__/event-classifier.test.ts +121 -121
  489. package/src/modules/audit/event-classifier.ts +155 -155
  490. package/src/modules/auth/__tests__/account-deactivation.test.ts +1 -1
  491. package/src/modules/auth/__tests__/account-erasure.approval-gate.test.ts +4 -4
  492. package/src/modules/auth/__tests__/account-erasure.participants.test.ts +56 -0
  493. package/src/modules/auth/__tests__/account-switch-lock.pg.test.ts +2 -2
  494. package/src/modules/auth/__tests__/account-switch-lock.test.ts +2 -2
  495. package/src/modules/auth/__tests__/account.routes.test.ts +73 -11
  496. package/src/modules/auth/__tests__/auth.service.test.ts +1 -1
  497. package/src/modules/auth/account-erasure.service.ts +27 -9
  498. package/src/modules/auth/account-hooks.ts +34 -0
  499. package/src/modules/auth/account-switch-lock.ts +2 -2
  500. package/src/modules/auth/account.routes.ts +23 -5
  501. package/src/modules/auth/auth.service.ts +4 -4
  502. package/src/modules/auth/index.ts +1 -0
  503. package/src/modules/database/__tests__/advisory-lock.test.ts +169 -169
  504. package/src/modules/database/__tests__/connection.pg.test.ts +55 -0
  505. package/src/modules/database/__tests__/connection.test.ts +30 -0
  506. package/src/modules/database/__tests__/migrations-unqualified.test.ts +32 -32
  507. package/src/modules/database/connection.ts +12 -0
  508. package/src/modules/database/core-schema.ts +2 -2
  509. package/src/modules/diff/__tests__/diff.routes.read-gate.test.ts +2 -2
  510. package/src/modules/diff/__tests__/diff.routes.rejectPathsLocked.test.ts +12 -12
  511. package/src/modules/diff/diff.routes.ts +8 -8
  512. package/src/modules/embed/__tests__/embed.service.lock.test.ts +7 -7
  513. package/src/modules/embed/__tests__/embed.service.test.ts +48 -62
  514. package/src/modules/embed/__tests__/embed.tools.test.ts +1 -1
  515. package/src/modules/embed/embed.routes.ts +1 -1
  516. package/src/modules/embed/embed.service.ts +17 -17
  517. package/src/modules/kb-fs/__tests__/branch-name.test.ts +2 -2
  518. package/src/modules/kb-fs/__tests__/file-change-notifier.test.ts +23 -4
  519. package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +264 -305
  520. package/src/modules/kb-fs/__tests__/move-with-edits.test.ts +103 -40
  521. package/src/modules/kb-fs/__tests__/repo-path.test.ts +8 -8
  522. package/src/modules/kb-fs/branch-name.ts +8 -8
  523. package/src/modules/kb-fs/file-change-notifier.ts +55 -32
  524. package/src/modules/kb-fs/locking-filesystem.ts +34 -48
  525. package/src/modules/kb-fs/repo-path.ts +5 -5
  526. package/src/modules/kb-fs/tmp-ref.ts +3 -3
  527. package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +36 -36
  528. package/src/modules/kb-sync/index.ts +1 -1
  529. package/src/modules/kb-sync/kb-sync.interface.ts +3 -3
  530. package/src/modules/kb-sync/kb-sync.service.ts +9 -9
  531. package/src/modules/marketplace/__tests__/github-facade.test.ts +1 -1
  532. package/src/modules/marketplace/__tests__/marketplace-git.test.ts +1 -1
  533. package/src/modules/mcp/__tests__/mcp-proxy-helpers.test.ts +4 -2
  534. package/src/modules/mcp/__tests__/mcp.e2e.test.ts +69 -8
  535. package/src/modules/mcp/__tests__/mcp.service.test.ts +91 -13
  536. package/src/modules/mcp/__tests__/read-only-hint.pg.test.ts +194 -0
  537. package/src/modules/mcp/mcp.service.ts +89 -28
  538. package/src/modules/onboarding/__tests__/starter-pack.service.test.ts +59 -59
  539. package/src/modules/onboarding/onboarding.contract.ts +1 -1
  540. package/src/modules/onboarding/onboarding.routes.ts +2 -2
  541. package/src/modules/onboarding/starter-pack.service.ts +7 -9
  542. package/src/modules/plugins/__tests__/bundle-dialect.e2e.test.ts +1 -1
  543. package/src/modules/plugins/__tests__/compile-marketplace.test.ts +1 -1
  544. package/src/modules/plugins/__tests__/join-request-jobs.service.test.ts +43 -43
  545. package/src/modules/plugins/__tests__/join-requests.service.test.ts +19 -19
  546. package/src/modules/plugins/__tests__/join-requests.settlement.test.ts +26 -26
  547. package/src/modules/plugins/__tests__/plugin-rename.service.test.ts +46 -21
  548. package/src/modules/plugins/__tests__/plugins.routes.test.ts +31 -31
  549. package/src/modules/plugins/join-request-jobs.service.ts +10 -10
  550. package/src/modules/plugins/join-requests.service.ts +4 -4
  551. package/src/modules/plugins/plugin-rename.service.ts +1 -1
  552. package/src/modules/plugins/plugins.routes.ts +9 -9
  553. package/src/modules/secrets-vault/__tests__/db-secrets-vault.managed.test.ts +773 -0
  554. package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +28 -1
  555. package/src/modules/secrets-vault/__tests__/managed-sign-in.route.test.ts +351 -0
  556. package/src/modules/secrets-vault/db-secrets-vault.service.ts +562 -95
  557. package/src/modules/secrets-vault/index.ts +7 -0
  558. package/src/modules/secrets-vault/managed-sign-in-erasure.ts +41 -0
  559. package/src/modules/secrets-vault/secrets-variable-loader.ts +134 -134
  560. package/src/modules/secrets-vault/secrets-vault.contract.ts +152 -0
  561. package/src/modules/secrets-vault/secrets-vault.routes.ts +157 -10
  562. package/src/modules/settings/__tests__/repository-source.test.ts +1 -1
  563. package/src/modules/settings/__tests__/setup.routes.layout-phase.test.ts +1 -1
  564. package/src/modules/settings/__tests__/setup.routes.managed-phase.test.ts +1 -1
  565. package/src/modules/settings/connection-check.ts +1 -1
  566. package/src/modules/settings/deployment-settings.service.ts +1 -1
  567. package/src/modules/settings/git-root-folders.ts +1 -1
  568. package/src/modules/settings/setup.routes.ts +1 -1
  569. package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +3 -3
  570. package/src/modules/skills/__tests__/branch-skills.tools.test.ts +1 -1
  571. package/src/modules/skills/__tests__/pending-skills.service.test.ts +8 -8
  572. package/src/modules/skills/__tests__/skill-access-requests.routes.test.ts +18 -18
  573. package/src/modules/skills/__tests__/skills.service.test.ts +3 -3
  574. package/src/modules/skills/pending-skills.service.ts +3 -3
  575. package/src/modules/skills/skill-access-requests.routes.ts +7 -7
  576. package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +4 -4
  577. package/src/modules/tool-helpers/__tests__/branch-declaration.test.ts +3 -3
  578. package/src/modules/tool-helpers/__tests__/can-write.tools.test.ts +3 -3
  579. package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +4 -4
  580. package/src/modules/tool-helpers/__tests__/tools-surface.test.ts +3 -3
  581. package/src/modules/tool-helpers/__tests__/validate-token.test.ts +1 -1
  582. package/src/modules/tool-helpers/tool-context.ts +6 -6
  583. package/src/modules/tool-helpers/tool-handler.ts +2 -2
  584. package/src/modules/tool-helpers/tool.contract.ts +4 -4
  585. package/src/modules/tool-manuals/__tests__/pending-tools.service.test.ts +9 -9
  586. package/src/modules/tool-manuals/__tests__/tool-manuals.managed-sign-in.test.ts +89 -0
  587. package/src/modules/tool-manuals/__tests__/tool-manuals.restart.test.ts +1 -1
  588. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +36 -1
  589. package/src/modules/tool-manuals/mcp-server-edit.service.ts +1 -1
  590. package/src/modules/tool-manuals/pending-tools.service.ts +3 -3
  591. package/src/modules/tool-manuals/tool-delete.service.ts +1 -1
  592. package/src/modules/tool-manuals/tool-manuals.contract.ts +41 -3
  593. package/src/modules/tool-manuals/tool-manuals.service.ts +53 -5
  594. package/src/modules/tool-manuals/tool-manuals.tools.ts +41 -15
  595. package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +4 -4
  596. package/src/modules/tool-registry/__tests__/read-only-hints.test.ts +3 -3
  597. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +6 -6
  598. package/src/modules/{workflow → version-control}/__tests__/apply-failure.test.ts +54 -54
  599. package/src/modules/{workflow → version-control}/__tests__/event-bus.test.ts +27 -27
  600. package/src/modules/{workflow → version-control}/__tests__/events-delivery.test.ts +37 -37
  601. package/src/modules/{workflow → version-control}/__tests__/file-lock.service.deletion-gate.test.ts +2 -2
  602. package/src/modules/{workflow → version-control}/__tests__/file-lock.service.path-identity.test.ts +8 -8
  603. package/src/modules/{workflow → version-control}/__tests__/merge-announces-tree.test.ts +11 -11
  604. package/src/modules/{workflow → version-control}/__tests__/pending-commits.worker.concurrency.test.ts +9 -9
  605. package/src/modules/{workflow → version-control}/__tests__/pending-commits.worker.test.ts +25 -25
  606. package/src/modules/{workflow → version-control}/__tests__/preserve-roles-yaml.test.ts +42 -42
  607. package/src/modules/version-control/__tests__/public-surface.test.ts +114 -0
  608. package/src/modules/{workflow/__tests__/workflow.routes.folder-proposals.test.ts → version-control/__tests__/version-control.routes.folder-proposals.test.ts} +18 -18
  609. package/src/modules/{workflow/__tests__/workflow.routes.fork-point-file.test.ts → version-control/__tests__/version-control.routes.fork-point-file.test.ts} +19 -19
  610. package/src/modules/{workflow/__tests__/workflow.routes.history-read-gate.test.ts → version-control/__tests__/version-control.routes.history-read-gate.test.ts} +13 -13
  611. package/src/modules/{workflow/__tests__/workflow.routes.lock-path.test.ts → version-control/__tests__/version-control.routes.lock-path.test.ts} +18 -18
  612. package/src/modules/{workflow/__tests__/workflow.routes.open-change-request.test.ts → version-control/__tests__/version-control.routes.open-change-request.test.ts} +7 -7
  613. package/src/modules/{workflow/__tests__/workflow.routes.push-refused.test.ts → version-control/__tests__/version-control.routes.push-refused.test.ts} +14 -14
  614. package/src/modules/{workflow/__tests__/workflow.service.branch-in-use.test.ts → version-control/__tests__/version-control.service.branch-in-use.test.ts} +6 -6
  615. package/src/modules/{workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts → version-control/__tests__/version-control.service.commitFileWhileLocked.test.ts} +14 -14
  616. package/src/modules/{workflow/__tests__/workflow.service.delete-branch.test.ts → version-control/__tests__/version-control.service.delete-branch.test.ts} +14 -14
  617. package/src/modules/{workflow/__tests__/workflow.service.deleted-branch-sweep.test.ts → version-control/__tests__/version-control.service.deleted-branch-sweep.test.ts} +4 -4
  618. package/src/modules/{workflow/__tests__/workflow.service.facade.test.ts → version-control/__tests__/version-control.service.facade.test.ts} +100 -102
  619. package/src/modules/{workflow/__tests__/workflow.service.folder-proposals.test.ts → version-control/__tests__/version-control.service.folder-proposals.test.ts} +7 -7
  620. package/src/modules/{workflow/__tests__/workflow.service.platform-restore.test.ts → version-control/__tests__/version-control.service.platform-restore.test.ts} +6 -6
  621. package/src/modules/{workflow/__tests__/workflow.service.read-gate.test.ts → version-control/__tests__/version-control.service.read-gate.test.ts} +9 -9
  622. package/src/modules/{workflow/__tests__/workflow.service.releaseLock.test.ts → version-control/__tests__/version-control.service.releaseLock.test.ts} +85 -75
  623. package/src/modules/{workflow/__tests__/workflow.service.repository-replaced.test.ts → version-control/__tests__/version-control.service.repository-replaced.test.ts} +4 -4
  624. package/src/modules/{workflow/__tests__/workflow.service.sync.test.ts → version-control/__tests__/version-control.service.sync.test.ts} +17 -17
  625. package/src/modules/{workflow/__tests__/workflow.service.update-change-request.pg.test.ts → version-control/__tests__/version-control.service.update-change-request.pg.test.ts} +6 -6
  626. package/src/modules/{workflow/__tests__/workflow.service.update-change-request.test.ts → version-control/__tests__/version-control.service.update-change-request.test.ts} +11 -11
  627. package/src/modules/{workflow/__tests__/workflow.service.update-from-target.test.ts → version-control/__tests__/version-control.service.update-from-target.test.ts} +22 -22
  628. package/src/modules/{workflow → version-control}/agent-tools/__tests__/change-request-read.tools.test.ts +3 -3
  629. package/src/modules/{workflow → version-control}/agent-tools/__tests__/update-change-request.parity.test.ts +16 -16
  630. package/src/modules/{workflow/agent-tools/__tests__/workflow.tools.test.ts → version-control/agent-tools/__tests__/version-control.tools.test.ts} +9 -9
  631. package/src/modules/{workflow → version-control}/agent-tools/change-request-read.tools.ts +2 -2
  632. package/src/modules/{workflow/agent-tools/workflow.tools.ts → version-control/agent-tools/version-control.tools.ts} +30 -30
  633. package/src/modules/{workflow → version-control}/change-request-hooks.ts +1 -1
  634. package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/approval-states.test.ts +5 -5
  635. package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/cancel-pr.test.ts +8 -8
  636. package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/carry-approvals-forward.test.ts +6 -6
  637. package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/erase-approver.test.ts +5 -5
  638. package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/merge-gate.test.ts +3 -3
  639. package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/merge-records-own-commit.test.ts +3 -3
  640. package/src/modules/{workflow/review-workflow/review-workflow.interface.ts → version-control/change-request-review/change-request-review.interface.ts} +1 -1
  641. package/src/modules/{workflow/review-workflow/review-workflow.service.ts → version-control/change-request-review/change-request-review.service.ts} +28 -28
  642. package/src/modules/{workflow → version-control}/event-bus.ts +14 -14
  643. package/src/modules/{workflow → version-control}/events-delivery.ts +11 -11
  644. package/src/modules/{workflow → version-control}/events.routes.ts +5 -5
  645. package/src/modules/{workflow → version-control}/file-lock.service.ts +6 -6
  646. package/src/modules/{workflow → version-control}/git/__tests__/git-test-helpers.ts +3 -3
  647. package/src/modules/{workflow → version-control}/git/__tests__/git.service.accessGating.test.ts +5 -5
  648. package/src/modules/{workflow → version-control}/git/__tests__/git.service.appliedChange.test.ts +4 -4
  649. package/src/modules/{workflow → version-control}/git/__tests__/git.service.changedFilesForPr.test.ts +16 -16
  650. package/src/modules/{workflow → version-control}/git/__tests__/git.service.changedPathsMemo.test.ts +2 -2
  651. package/src/modules/{workflow → version-control}/git/__tests__/git.service.commitChanges.test.ts +3 -3
  652. package/src/modules/{workflow → version-control}/git/__tests__/git.service.commitFile.strayPath.test.ts +10 -10
  653. package/src/modules/{workflow → version-control}/git/__tests__/git.service.commitFile.test.ts +3 -3
  654. package/src/modules/version-control/git/__tests__/git.service.commitLanded.test.ts +199 -0
  655. package/src/modules/{workflow → version-control}/git/__tests__/git.service.createBranch.test.ts +3 -3
  656. package/src/modules/{workflow → version-control}/git/__tests__/git.service.deleteBranch.test.ts +7 -7
  657. package/src/modules/{workflow → version-control}/git/__tests__/git.service.diffFileAtCommit.test.ts +9 -9
  658. package/src/modules/{workflow → version-control}/git/__tests__/git.service.diffFileBetweenBranches.test.ts +23 -23
  659. package/src/modules/{workflow → version-control}/git/__tests__/git.service.fileBytesAtCommit.test.ts +7 -7
  660. package/src/modules/{workflow → version-control}/git/__tests__/git.service.fileContentsAtCommit.test.ts +3 -3
  661. package/src/modules/{workflow → version-control}/git/__tests__/git.service.forkPoint.test.ts +2 -2
  662. package/src/modules/{workflow → version-control}/git/__tests__/git.service.history-guards.test.ts +2 -2
  663. package/src/modules/{workflow → version-control}/git/__tests__/git.service.list-branches.test.ts +12 -12
  664. package/src/modules/{workflow → version-control}/git/__tests__/git.service.listTrackedFiles.test.ts +2 -2
  665. package/src/modules/{workflow → version-control}/git/__tests__/git.service.logForFile.test.ts +6 -6
  666. package/src/modules/{workflow → version-control}/git/__tests__/git.service.mergeChangeRequest.test.ts +99 -22
  667. package/src/modules/{workflow → version-control}/git/__tests__/git.service.mergeIntoBranch.test.ts +16 -16
  668. package/src/modules/{workflow → version-control}/git/__tests__/git.service.pendingChanges.test.ts +2 -2
  669. package/src/modules/{workflow → version-control}/git/__tests__/git.service.postMergePull.test.ts +5 -5
  670. package/src/modules/{workflow → version-control}/git/__tests__/git.service.prFetchFailure.test.ts +2 -2
  671. package/src/modules/{workflow → version-control}/git/__tests__/git.service.pull.test.ts +12 -12
  672. package/src/modules/{workflow → version-control}/git/__tests__/git.service.readFileAtRef.test.ts +3 -3
  673. package/src/modules/{workflow → version-control}/git/__tests__/git.service.revertPathsAndPush.test.ts +2 -2
  674. package/src/modules/{workflow → version-control}/git/__tests__/git.service.status.test.ts +4 -4
  675. package/src/modules/{workflow → version-control}/git/__tests__/git.service.syncFromRemote.test.ts +8 -8
  676. package/src/modules/{workflow → version-control}/git/__tests__/git.service.worktrees.test.ts +4 -4
  677. package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.list-fetch.test.ts +2 -2
  678. package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.test.ts +6 -6
  679. package/src/modules/{workflow → version-control}/git/git.service.ts +112 -59
  680. package/src/modules/{workflow → version-control}/git/node-git-runner.ts +1 -1
  681. package/src/modules/{workflow → version-control}/git/pull-request.service.ts +12 -12
  682. package/src/modules/{workflow → version-control}/index.ts +6 -5
  683. package/src/modules/{workflow → version-control}/pending-commits.service.ts +2 -2
  684. package/src/modules/{workflow → version-control}/pending-commits.worker.ts +8 -8
  685. package/src/modules/{workflow → version-control}/read-on-branch.ts +2 -2
  686. package/src/modules/{workflow/workflow-hooks.ts → version-control/version-control-hooks.ts} +44 -5
  687. package/src/modules/{workflow/workflow.routes.ts → version-control/version-control.routes.ts} +64 -63
  688. package/src/modules/{workflow/workflow.service.ts → version-control/version-control.service.ts} +96 -109
  689. package/src/modules/workspace/__tests__/agent-access.gate.test.ts +8 -8
  690. package/src/modules/workspace/__tests__/agent-downloads.test.ts +13 -16
  691. package/src/modules/workspace/__tests__/agent-uploads.test.ts +11 -12
  692. package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +11 -16
  693. package/src/modules/workspace/__tests__/file-stat-access.test.ts +3 -3
  694. package/src/modules/workspace/__tests__/git-internals.security.test.ts +27 -27
  695. package/src/modules/workspace/__tests__/kb-repository.test.ts +1 -1
  696. package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +3 -3
  697. package/src/modules/workspace/__tests__/start-session.default-branch.test.ts +152 -0
  698. package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +6 -6
  699. package/src/modules/workspace/__tests__/workspace-tmp.tools.test.ts +13 -14
  700. package/src/modules/workspace/__tests__/workspace.routes.at-ref.test.ts +6 -6
  701. package/src/modules/workspace/__tests__/workspace.routes.branch-errors.test.ts +4 -4
  702. package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +6 -6
  703. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +8 -8
  704. package/src/modules/workspace/__tests__/workspace.routes.download-implies-read.test.ts +4 -4
  705. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +6 -6
  706. package/src/modules/workspace/__tests__/workspace.routes.file-writes.test.ts +6 -6
  707. package/src/modules/workspace/__tests__/workspace.routes.folder-zip-access.test.ts +4 -4
  708. package/src/modules/workspace/__tests__/workspace.routes.folders.test.ts +6 -6
  709. package/src/modules/workspace/__tests__/workspace.routes.move-platform-files.test.ts +7 -7
  710. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +13 -13
  711. package/src/modules/workspace/__tests__/workspace.service.any-workspace-credentials.test.ts +1 -1
  712. package/src/modules/workspace/__tests__/workspace.service.replaced-repository.test.ts +1 -1
  713. package/src/modules/workspace/__tests__/workspace.service.worktrees.test.ts +1 -1
  714. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +5 -5
  715. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +5 -5
  716. package/src/modules/workspace/__tests__/workspace.tools.test.ts +27 -27
  717. package/src/modules/workspace/agent-access.gate.ts +5 -5
  718. package/src/modules/workspace/git-internals.middleware.ts +2 -2
  719. package/src/modules/workspace/kb-repository.ts +1 -1
  720. package/src/modules/workspace/startup/__tests__/kb-startup-runner.degraded.test.ts +1 -1
  721. package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +1 -1
  722. package/src/modules/workspace/startup/__tests__/kb-startup-runner.worktrees.test.ts +1 -1
  723. package/src/modules/workspace/startup/kb-git.ts +1 -1
  724. package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +94 -70
  725. package/src/modules/workspace/startup/steps/seed-tree.ts +4 -5
  726. package/src/modules/workspace/startup/steps/template-files.step.ts +4 -5
  727. package/src/modules/workspace/startup/steps/template-source.ts +21 -6
  728. package/src/modules/workspace/workspace-tmp.ts +4 -4
  729. package/src/modules/workspace/workspace.routes.ts +26 -26
  730. package/src/modules/workspace/workspace.service.ts +11 -11
  731. package/src/modules/workspace/workspace.tools.ts +17 -10
  732. package/src/shared/__tests__/canonical-file-identity.test.ts +5 -5
  733. package/src/shared/__tests__/fire-and-forget.test.ts +39 -0
  734. package/src/shared/__tests__/http-errors.test.ts +4 -4
  735. package/src/shared/canonical-file-identity.ts +5 -5
  736. package/src/shared/domain-errors.ts +44 -44
  737. package/src/shared/fire-and-forget.ts +26 -0
  738. package/src/shared/git.contract.ts +1 -1
  739. package/src/shared/http-errors.ts +3 -3
  740. package/src/shared/kb-context.ts +130 -130
  741. package/src/shared/pending-proposals.ts +4 -4
  742. package/src/tenancy/__tests__/tenant-host.test.ts +372 -372
  743. package/src/tenancy/__tests__/tenant-runtime.test.ts +221 -221
  744. package/src/tenancy/__tests__/tenant-secrets.test.ts +35 -35
  745. package/src/tenancy/index.ts +35 -35
  746. package/src/tenancy/static-tenant-source.ts +1 -1
  747. package/src/tenancy/tenant-host.ts +261 -261
  748. package/src/tenancy/tenant-runtime.ts +208 -208
  749. package/src/tenancy/tenant-source.contract.ts +59 -59
  750. package/dist/modules/workflow/agent-tools/change-request-read-shape.d.ts.map +0 -1
  751. package/dist/modules/workflow/agent-tools/change-request-read-shape.js.map +0 -1
  752. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +0 -1
  753. package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +0 -1
  754. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +0 -1
  755. package/dist/modules/workflow/agent-tools/change-request-summary.js.map +0 -1
  756. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +0 -1
  757. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +0 -1
  758. package/dist/modules/workflow/change-request-hooks.d.ts.map +0 -1
  759. package/dist/modules/workflow/change-request-hooks.js.map +0 -1
  760. package/dist/modules/workflow/event-bus.d.ts.map +0 -1
  761. package/dist/modules/workflow/event-bus.js.map +0 -1
  762. package/dist/modules/workflow/events-delivery.d.ts.map +0 -1
  763. package/dist/modules/workflow/events-delivery.js.map +0 -1
  764. package/dist/modules/workflow/events.routes.d.ts.map +0 -1
  765. package/dist/modules/workflow/events.routes.js.map +0 -1
  766. package/dist/modules/workflow/file-lock.service.d.ts.map +0 -1
  767. package/dist/modules/workflow/file-lock.service.js.map +0 -1
  768. package/dist/modules/workflow/git/change-request-link.d.ts.map +0 -1
  769. package/dist/modules/workflow/git/change-request-link.js.map +0 -1
  770. package/dist/modules/workflow/git/git-version.d.ts.map +0 -1
  771. package/dist/modules/workflow/git/git-version.js.map +0 -1
  772. package/dist/modules/workflow/git/git.service.d.ts.map +0 -1
  773. package/dist/modules/workflow/git/git.service.js.map +0 -1
  774. package/dist/modules/workflow/git/merge-commit.d.ts.map +0 -1
  775. package/dist/modules/workflow/git/merge-commit.js.map +0 -1
  776. package/dist/modules/workflow/git/node-git-runner.d.ts.map +0 -1
  777. package/dist/modules/workflow/git/node-git-runner.js.map +0 -1
  778. package/dist/modules/workflow/git/pull-request.service.d.ts.map +0 -1
  779. package/dist/modules/workflow/git/pull-request.service.js.map +0 -1
  780. package/dist/modules/workflow/index.d.ts.map +0 -1
  781. package/dist/modules/workflow/index.js.map +0 -1
  782. package/dist/modules/workflow/pending-commits.service.d.ts.map +0 -1
  783. package/dist/modules/workflow/pending-commits.service.js.map +0 -1
  784. package/dist/modules/workflow/pending-commits.worker.d.ts.map +0 -1
  785. package/dist/modules/workflow/pending-commits.worker.js.map +0 -1
  786. package/dist/modules/workflow/read-on-branch.d.ts.map +0 -1
  787. package/dist/modules/workflow/read-on-branch.js.map +0 -1
  788. package/dist/modules/workflow/recovery-bot.d.ts.map +0 -1
  789. package/dist/modules/workflow/recovery-bot.js.map +0 -1
  790. package/dist/modules/workflow/review-workflow/approval-lock.d.ts.map +0 -1
  791. package/dist/modules/workflow/review-workflow/approval-lock.js.map +0 -1
  792. package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts.map +0 -1
  793. package/dist/modules/workflow/review-workflow/review-workflow.interface.js +0 -2
  794. package/dist/modules/workflow/review-workflow/review-workflow.interface.js.map +0 -1
  795. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +0 -1
  796. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +0 -1
  797. package/dist/modules/workflow/sanitize-error.d.ts.map +0 -1
  798. package/dist/modules/workflow/sanitize-error.js.map +0 -1
  799. package/dist/modules/workflow/workflow-hooks.d.ts.map +0 -1
  800. package/dist/modules/workflow/workflow-hooks.js.map +0 -1
  801. package/dist/modules/workflow/workflow.routes.d.ts.map +0 -1
  802. package/dist/modules/workflow/workflow.routes.js.map +0 -1
  803. package/dist/modules/workflow/workflow.service.d.ts.map +0 -1
  804. package/dist/modules/workflow/workflow.service.js.map +0 -1
  805. package/src/modules/mcp/__tests__/read-only-hint.e2e.test.ts +0 -213
  806. /package/dist/modules/{workflow → version-control}/agent-tools/change-request-read-shape.d.ts +0 -0
  807. /package/dist/modules/{workflow → version-control}/agent-tools/change-request-read-shape.js +0 -0
  808. /package/dist/modules/{workflow → version-control}/agent-tools/change-request-read.tools.d.ts +0 -0
  809. /package/dist/modules/{workflow → version-control}/agent-tools/change-request-summary.d.ts +0 -0
  810. /package/dist/modules/{workflow → version-control}/agent-tools/change-request-summary.js +0 -0
  811. /package/dist/modules/{workflow → version-control}/change-request-hooks.js +0 -0
  812. /package/dist/modules/{workflow/review-workflow → version-control/change-request-review}/approval-lock.d.ts +0 -0
  813. /package/dist/modules/{workflow/review-workflow → version-control/change-request-review}/approval-lock.js +0 -0
  814. /package/dist/modules/{workflow → version-control}/events-delivery.js +0 -0
  815. /package/dist/modules/{workflow → version-control}/events.routes.js +0 -0
  816. /package/dist/modules/{workflow → version-control}/git/change-request-link.d.ts +0 -0
  817. /package/dist/modules/{workflow → version-control}/git/change-request-link.js +0 -0
  818. /package/dist/modules/{workflow → version-control}/git/git-version.d.ts +0 -0
  819. /package/dist/modules/{workflow → version-control}/git/git-version.js +0 -0
  820. /package/dist/modules/{workflow → version-control}/git/merge-commit.d.ts +0 -0
  821. /package/dist/modules/{workflow → version-control}/git/merge-commit.js +0 -0
  822. /package/dist/modules/{workflow → version-control}/git/node-git-runner.d.ts +0 -0
  823. /package/dist/modules/{workflow → version-control}/recovery-bot.d.ts +0 -0
  824. /package/dist/modules/{workflow → version-control}/recovery-bot.js +0 -0
  825. /package/dist/modules/{workflow → version-control}/sanitize-error.d.ts +0 -0
  826. /package/dist/modules/{workflow → version-control}/sanitize-error.js +0 -0
  827. /package/src/modules/{workflow → version-control}/__tests__/change-request-hooks.test.ts +0 -0
  828. /package/src/modules/{workflow → version-control}/__tests__/fake-file-lock-db.ts +0 -0
  829. /package/src/modules/{workflow → version-control}/__tests__/pending-commits.canonical.test.ts +0 -0
  830. /package/src/modules/{workflow → version-control}/__tests__/pending-commits.enqueue-if-absent.test.ts +0 -0
  831. /package/src/modules/{workflow → version-control}/__tests__/pending-commits.repository-replaced.test.ts +0 -0
  832. /package/src/modules/{workflow → version-control}/__tests__/sanitize-error.test.ts +0 -0
  833. /package/src/modules/{workflow → version-control}/agent-tools/__tests__/change-request-read-shape.test.ts +0 -0
  834. /package/src/modules/{workflow → version-control}/agent-tools/change-request-read-shape.ts +0 -0
  835. /package/src/modules/{workflow → version-control}/agent-tools/change-request-summary.ts +0 -0
  836. /package/src/modules/{workflow/review-workflow → version-control/change-request-review}/approval-lock.ts +0 -0
  837. /package/src/modules/{workflow → version-control}/git/__tests__/branchAuthor.test.ts +0 -0
  838. /package/src/modules/{workflow → version-control}/git/__tests__/change-request-link.test.ts +0 -0
  839. /package/src/modules/{workflow → version-control}/git/__tests__/git-version.failure.test.ts +0 -0
  840. /package/src/modules/{workflow → version-control}/git/__tests__/git-version.test.ts +0 -0
  841. /package/src/modules/{workflow → version-control}/git/__tests__/node-git-runner.test.ts +0 -0
  842. /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.getPrDetail.test.ts +0 -0
  843. /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.placeholder.test.ts +0 -0
  844. /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.viewer-can-cancel.test.ts +0 -0
  845. /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.viewer-can-delete.test.ts +0 -0
  846. /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.viewer-can-update.test.ts +0 -0
  847. /package/src/modules/{workflow → version-control}/git/change-request-link.ts +0 -0
  848. /package/src/modules/{workflow → version-control}/git/git-version.ts +0 -0
  849. /package/src/modules/{workflow → version-control}/git/merge-commit.ts +0 -0
  850. /package/src/modules/{workflow → version-control}/recovery-bot.ts +0 -0
  851. /package/src/modules/{workflow → version-control}/sanitize-error.ts +0 -0
@@ -2,15 +2,11 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
2
  import fs from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import os from 'node:os';
5
- import type { AuthUser, IWorkflowService } from '@bevel-software/platform-shared';
5
+ import type { AuthUser, IVersionControlService } from '@bevel-software/platform-shared';
6
6
  import { LocalFilesystem } from '@mastra/core/workspace';
7
7
  import { LockingFilesystem } from '../locking-filesystem.js';
8
- import { PushNeedsAgentResolutionError, WorkflowValidationError } from '../../../shared/domain-errors.js';
9
- import {
10
- makeAgentRolesYamlWriteValidator,
11
- RolesYamlInvalidError,
12
- RolesYamlNewRoleError,
13
- } from '../../access-model/roles-yaml-guard.js';
8
+ import { PushNeedsAgentResolutionError, VersionControlValidationError } from '../../../shared/domain-errors.js';
9
+ import { makeAgentRolesYamlWriteValidator } from '../../access-model/index.js';
14
10
 
15
11
  /** The clone folder at the workspace root: every path the filesystem mutates lives under it. */
16
12
  const KB = 'knowledge-base';
@@ -21,7 +17,7 @@ const USER: AuthUser = {
21
17
  name: 'Alice',
22
18
  };
23
19
 
24
- function makeWorkflow(): IWorkflowService {
20
+ function makeVersionControl(): IVersionControlService {
25
21
  return {
26
22
  acquireLock: vi.fn().mockResolvedValue({
27
23
  acquired: true,
@@ -42,7 +38,7 @@ function makeWorkflow(): IWorkflowService {
42
38
  getLock: vi.fn(),
43
39
  commitFileWhileLocked: vi.fn(),
44
40
  commitChanges: vi.fn().mockResolvedValue({}),
45
- } as unknown as IWorkflowService;
41
+ } as unknown as IVersionControlService;
46
42
  }
47
43
 
48
44
  async function mkTmpRoot(): Promise<string> {
@@ -61,29 +57,29 @@ describe('LockingFilesystem — every mutating op runs acquire → super → rel
61
57
  });
62
58
 
63
59
  it('writeFile acquires, writes through to disk, then releases', async () => {
64
- const workflow = makeWorkflow();
60
+ const versionControl = makeVersionControl();
65
61
  const fsLayer = new LockingFilesystem(
66
62
  { basePath: root, contained: true },
67
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
63
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
68
64
  );
69
65
  await fsLayer.writeFile('knowledge-base/Knowledge/Foo.md', 'hello\n');
70
66
 
71
- expect(workflow.acquireLock).toHaveBeenCalledWith(
67
+ expect(versionControl.acquireLock).toHaveBeenCalledWith(
72
68
  'ws-feat',
73
69
  'feat',
74
70
  'knowledge-base/Knowledge/Foo.md',
75
71
  USER,
76
72
  );
77
- expect(workflow.releaseLock).toHaveBeenCalledWith(
73
+ expect(versionControl.releaseLock).toHaveBeenCalledWith(
78
74
  'ws-feat',
79
75
  'feat',
80
76
  'knowledge-base/Knowledge/Foo.md',
81
77
  USER,
82
78
  );
83
79
  // Acquire fires before release.
84
- const acquireOrder = (workflow.acquireLock as ReturnType<typeof vi.fn>).mock
80
+ const acquireOrder = (versionControl.acquireLock as ReturnType<typeof vi.fn>).mock
85
81
  .invocationCallOrder[0];
86
- const releaseOrder = (workflow.releaseLock as ReturnType<typeof vi.fn>).mock
82
+ const releaseOrder = (versionControl.releaseLock as ReturnType<typeof vi.fn>).mock
87
83
  .invocationCallOrder[0];
88
84
  expect(acquireOrder).toBeLessThan(releaseOrder);
89
85
  // The bytes actually landed on disk.
@@ -94,33 +90,33 @@ describe('LockingFilesystem — every mutating op runs acquire → super → rel
94
90
  const filePath = path.join(root, 'knowledge-base/a.md');
95
91
  await fs.mkdir(path.dirname(filePath), { recursive: true });
96
92
  await fs.writeFile(filePath, 'x', 'utf-8');
97
- const workflow = makeWorkflow();
93
+ const versionControl = makeVersionControl();
98
94
  const fsLayer = new LockingFilesystem(
99
95
  { basePath: root, contained: true },
100
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
96
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
101
97
  );
102
98
  await fsLayer.deleteFile('knowledge-base/a.md');
103
- expect(workflow.acquireLock).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/a.md', USER);
104
- expect(workflow.releaseLock).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/a.md', USER);
99
+ expect(versionControl.acquireLock).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/a.md', USER);
100
+ expect(versionControl.releaseLock).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/a.md', USER);
105
101
  await expect(fs.access(filePath)).rejects.toBeDefined();
106
102
  });
107
103
 
108
104
  it('mkdir drops a .gitkeep through the lock-aware writeFile so the empty folder is committed', async () => {
109
- const workflow = makeWorkflow();
105
+ const versionControl = makeVersionControl();
110
106
  const fsLayer = new LockingFilesystem(
111
107
  { basePath: root, contained: true },
112
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
108
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
113
109
  );
114
110
  await fsLayer.mkdir('knowledge-base/empty');
115
111
  // .gitkeep got written through the lock path. The lock is on the
116
112
  // .gitkeep path (that's the file the commit attaches to).
117
- expect(workflow.acquireLock).toHaveBeenCalledWith(
113
+ expect(versionControl.acquireLock).toHaveBeenCalledWith(
118
114
  'ws-feat',
119
115
  'feat',
120
116
  'knowledge-base/empty/.gitkeep',
121
117
  USER,
122
118
  );
123
- expect(workflow.releaseLock).toHaveBeenCalledWith(
119
+ expect(versionControl.releaseLock).toHaveBeenCalledWith(
124
120
  'ws-feat',
125
121
  'feat',
126
122
  'knowledge-base/empty/.gitkeep',
@@ -132,37 +128,37 @@ describe('LockingFilesystem — every mutating op runs acquire → super → rel
132
128
  it('mkdir of an already-populated dir does NOT drop a .gitkeep', async () => {
133
129
  await fs.mkdir(path.join(root, 'knowledge-base/has-content'), { recursive: true });
134
130
  await fs.writeFile(path.join(root, 'knowledge-base/has-content/real.md'), 'hi', 'utf-8');
135
- const workflow = makeWorkflow();
131
+ const versionControl = makeVersionControl();
136
132
  const fsLayer = new LockingFilesystem(
137
133
  { basePath: root, contained: true },
138
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
134
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
139
135
  );
140
136
  await fsLayer.mkdir('knowledge-base/has-content', { recursive: true });
141
- expect(workflow.acquireLock).not.toHaveBeenCalled();
142
- expect(workflow.releaseLock).not.toHaveBeenCalled();
137
+ expect(versionControl.acquireLock).not.toHaveBeenCalled();
138
+ expect(versionControl.releaseLock).not.toHaveBeenCalled();
143
139
  // The existing file is untouched.
144
140
  expect(await fs.readFile(path.join(root, 'knowledge-base/has-content/real.md'), 'utf-8')).toBe('hi');
145
141
  });
146
142
 
147
143
  it('rmdir is refused with a clear error — one-change-per-file invariant', async () => {
148
144
  await fs.mkdir(path.join(root, 'knowledge-base/doomed'), { recursive: true });
149
- const workflow = makeWorkflow();
145
+ const versionControl = makeVersionControl();
150
146
  const fsLayer = new LockingFilesystem(
151
147
  { basePath: root, contained: true },
152
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
148
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
153
149
  );
154
150
  await expect(fsLayer.rmdir('knowledge-base/doomed')).rejects.toThrow(
155
151
  /Recursive directory removal is not supported/i,
156
152
  );
157
- expect(workflow.acquireLock).not.toHaveBeenCalled();
153
+ expect(versionControl.acquireLock).not.toHaveBeenCalled();
158
154
  });
159
155
 
160
156
  it(
161
157
  'retries acquire on contention then surfaces a skip error after 3 attempts',
162
158
  async () => {
163
159
  // 3 attempts × 2s ≈ 6s — bump vitest's default timeout for this one test.
164
- const workflow = makeWorkflow();
165
- (workflow.acquireLock as ReturnType<typeof vi.fn>).mockResolvedValue({
160
+ const versionControl = makeVersionControl();
161
+ (versionControl.acquireLock as ReturnType<typeof vi.fn>).mockResolvedValue({
166
162
  acquired: false,
167
163
  lock: {
168
164
  branch: 'feat',
@@ -176,23 +172,23 @@ describe('LockingFilesystem — every mutating op runs acquire → super → rel
176
172
  });
177
173
  const fsLayer = new LockingFilesystem(
178
174
  { basePath: root, contained: true },
179
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
175
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
180
176
  );
181
177
 
182
178
  await expect(fsLayer.writeFile('knowledge-base/Locked.md', 'x')).rejects.toThrow(
183
179
  /Skipped editing "knowledge-base\/Locked\.md" — locked by Bob/,
184
180
  );
185
- expect(workflow.acquireLock).toHaveBeenCalledTimes(3);
186
- expect(workflow.releaseLock).not.toHaveBeenCalled();
181
+ expect(versionControl.acquireLock).toHaveBeenCalledTimes(3);
182
+ expect(versionControl.releaseLock).not.toHaveBeenCalled();
187
183
  },
188
184
  10_000,
189
185
  );
190
186
 
191
187
  it('drops the lock without committing when the underlying write throws', async () => {
192
- const workflow = makeWorkflow();
188
+ const versionControl = makeVersionControl();
193
189
  const fsLayer = new LockingFilesystem(
194
190
  { basePath: root, contained: true },
195
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
191
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
196
192
  );
197
193
  // The underlying write dies (disk full, permissions, whatever). The lock
198
194
  // must still go away so the next caller can edit a different file, but
@@ -207,13 +203,13 @@ describe('LockingFilesystem — every mutating op runs acquire → super → rel
207
203
  } finally {
208
204
  spy.mockRestore();
209
205
  }
210
- expect(workflow.acquireLock).toHaveBeenCalled();
211
- expect(workflow.releaseLockNoCommit).toHaveBeenCalled();
212
- expect(workflow.releaseLock).not.toHaveBeenCalled();
206
+ expect(versionControl.acquireLock).toHaveBeenCalled();
207
+ expect(versionControl.releaseLockNoCommit).toHaveBeenCalled();
208
+ expect(versionControl.releaseLock).not.toHaveBeenCalled();
213
209
  });
214
210
  });
215
211
 
216
- describe('LockingFilesystem.writeFiles — batch with deletes + one batched change event', () => {
212
+ describe('LockingFilesystem.writeFiles — batch with deletes', () => {
217
213
  let root: string;
218
214
 
219
215
  beforeEach(async () => {
@@ -224,20 +220,18 @@ describe('LockingFilesystem.writeFiles — batch with deletes + one batched chan
224
220
  await fs.rm(root, { recursive: true, force: true });
225
221
  });
226
222
 
227
- it('locks writes AND deletes, applies both, emits ONE batched onFilesChanged', async () => {
223
+ it('locks writes AND deletes, applies both, returns the commit', async () => {
228
224
  await fs.mkdir(path.join(root, 'knowledge-base/Tools'), { recursive: true });
229
225
  await fs.writeFile(path.join(root, 'knowledge-base/Tools/old.tool'), '{}');
230
226
 
231
- const workflow = makeWorkflow();
232
- const emitted: unknown[] = [];
227
+ const versionControl = makeVersionControl();
233
228
  const fsLayer = new LockingFilesystem(
234
229
  { basePath: root, contained: true },
235
230
  {
236
- workflow,
231
+ versionControl,
237
232
  workspaceId: 'ws-feat',
238
233
  branch: 'feat',
239
234
  user: USER, kbDirName: KB,
240
- fileChanges: { emit: (c: unknown) => emitted.push(c) } as never,
241
235
  },
242
236
  );
243
237
 
@@ -248,36 +242,26 @@ describe('LockingFilesystem.writeFiles — batch with deletes + one batched chan
248
242
  );
249
243
 
250
244
  // Both paths locked; both mutations landed; commit returned.
251
- expect(workflow.acquireLock).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Tools/new.tool', USER);
252
- expect(workflow.acquireLock).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Tools/old.tool', USER);
245
+ expect(versionControl.acquireLock).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Tools/new.tool', USER);
246
+ expect(versionControl.acquireLock).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Tools/old.tool', USER);
253
247
  expect(await fs.readFile(path.join(root, 'knowledge-base/Tools/new.tool'), 'utf-8')).toContain('http');
254
248
  await expect(fs.access(path.join(root, 'knowledge-base/Tools/old.tool'))).rejects.toThrow();
255
249
  expect(change).toEqual({});
256
- // ONE event carrying the whole batch (sorted), attributed to the user.
257
- expect(emitted).toHaveLength(1);
258
- expect(emitted[0]).toMatchObject({
259
- workspaceId: 'ws-feat',
260
- branch: 'feat',
261
- paths: ['knowledge-base/Tools/new.tool', 'knowledge-base/Tools/old.tool'],
262
- byUser: USER,
263
- });
264
250
  });
265
251
 
266
- it('a failing commit releases every lock WITHOUT committing and emits nothing', async () => {
252
+ it('a failing commit releases every lock WITHOUT committing', async () => {
267
253
  await fs.mkdir(path.join(root, 'knowledge-base/Tools'), { recursive: true });
268
254
  await fs.writeFile(path.join(root, 'knowledge-base/Tools/old.tool'), '{}');
269
255
 
270
- const workflow = makeWorkflow();
271
- (workflow.commitChanges as ReturnType<typeof vi.fn>).mockRejectedValue(new Error('push exploded'));
272
- const emitted: unknown[] = [];
256
+ const versionControl = makeVersionControl();
257
+ (versionControl.commitChanges as ReturnType<typeof vi.fn>).mockRejectedValue(new Error('push exploded'));
273
258
  const fsLayer = new LockingFilesystem(
274
259
  { basePath: root, contained: true },
275
260
  {
276
- workflow,
261
+ versionControl,
277
262
  workspaceId: 'ws-feat',
278
263
  branch: 'feat',
279
264
  user: USER, kbDirName: KB,
280
- fileChanges: { emit: (c: unknown) => emitted.push(c) } as never,
281
265
  },
282
266
  );
283
267
 
@@ -291,12 +275,11 @@ describe('LockingFilesystem.writeFiles — batch with deletes + one batched chan
291
275
 
292
276
  // Every acquired path goes through the NO-COMMIT release — which is what
293
277
  // discards the uncommitted bytes (write reverted, delete restored) in the
294
- // real WorkflowService via git.discardPath. The committing release must
295
- // never run, and no change event fires for a failed batch.
296
- expect(workflow.releaseLockNoCommit).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Tools/new.tool', USER);
297
- expect(workflow.releaseLockNoCommit).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Tools/old.tool', USER);
298
- expect(workflow.releaseLock).not.toHaveBeenCalled();
299
- expect(emitted).toHaveLength(0);
278
+ // real VersionControlService via git.discardPath. The committing release must
279
+ // never run.
280
+ expect(versionControl.releaseLockNoCommit).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Tools/new.tool', USER);
281
+ expect(versionControl.releaseLockNoCommit).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Tools/old.tool', USER);
282
+ expect(versionControl.releaseLock).not.toHaveBeenCalled();
300
283
  });
301
284
 
302
285
  it('a POST-commit push failure releases WITH commit-on-release (arms the worker retry), never the discard path', async () => {
@@ -307,19 +290,17 @@ describe('LockingFilesystem.writeFiles — batch with deletes + one batched chan
307
290
  // forever. The batch must releaseLock (the enqueued release commit no-ops
308
291
  // on the clean tree and the worker's unpushed-commits check re-runs the
309
292
  // push ladder) and still propagate the error.
310
- const workflow = makeWorkflow();
311
- (workflow.commitChanges as ReturnType<typeof vi.fn>).mockRejectedValue(
293
+ const versionControl = makeVersionControl();
294
+ (versionControl.commitChanges as ReturnType<typeof vi.fn>).mockRejectedValue(
312
295
  new PushNeedsAgentResolutionError('feat', 'knowledge-base/A.md', 'non-fast-forward', 'rebase failed'),
313
296
  );
314
- const emitted: unknown[] = [];
315
297
  const fsLayer = new LockingFilesystem(
316
298
  { basePath: root, contained: true },
317
299
  {
318
- workflow,
300
+ versionControl,
319
301
  workspaceId: 'ws-feat',
320
302
  branch: 'feat',
321
303
  user: USER, kbDirName: KB,
322
- fileChanges: { emit: (c: unknown) => emitted.push(c) } as never,
323
304
  },
324
305
  );
325
306
 
@@ -327,27 +308,8 @@ describe('LockingFilesystem.writeFiles — batch with deletes + one batched chan
327
308
  fsLayer.writeFiles([{ path: 'knowledge-base/A.md', content: 'x' }], 'batch'),
328
309
  ).rejects.toBeInstanceOf(PushNeedsAgentResolutionError);
329
310
 
330
- expect(workflow.releaseLock).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/A.md', USER);
331
- expect(workflow.releaseLockNoCommit).not.toHaveBeenCalled();
332
- expect(emitted).toHaveLength(0);
333
- });
334
-
335
- it('emits nothing on a no-op commit (clean tree)', async () => {
336
- const workflow = makeWorkflow();
337
- (workflow.commitChanges as ReturnType<typeof vi.fn>).mockResolvedValue(null);
338
- const emitted: unknown[] = [];
339
- const fsLayer = new LockingFilesystem(
340
- { basePath: root, contained: true },
341
- {
342
- workflow,
343
- workspaceId: 'ws-feat',
344
- branch: 'feat',
345
- user: USER, kbDirName: KB,
346
- fileChanges: { emit: (c: unknown) => emitted.push(c) } as never,
347
- },
348
- );
349
- await fsLayer.writeFiles([{ path: 'knowledge-base/A.md', content: 'same' }], 'noop');
350
- expect(emitted).toHaveLength(0);
311
+ expect(versionControl.releaseLock).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/A.md', USER);
312
+ expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
351
313
  });
352
314
  });
353
315
 
@@ -362,24 +324,24 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
362
324
  await fs.rm(root, { recursive: true, force: true });
363
325
  });
364
326
 
365
- const layer = (workflow: IWorkflowService): LockingFilesystem =>
327
+ const layer = (versionControl: IVersionControlService): LockingFilesystem =>
366
328
  new LockingFilesystem(
367
329
  { basePath: root, contained: true },
368
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
330
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
369
331
  );
370
332
 
371
333
  it('writeFile runs the check with the lock HELD and before any byte lands', async () => {
372
334
  await fs.mkdir(path.join(root, 'knowledge-base'), { recursive: true });
373
335
  await fs.writeFile(path.join(root, 'knowledge-base/Foo.md'), 'before\n');
374
336
 
375
- const workflow = makeWorkflow();
337
+ const versionControl = makeVersionControl();
376
338
  const order: string[] = [];
377
- (workflow.acquireLock as ReturnType<typeof vi.fn>).mockImplementation(async () => {
339
+ (versionControl.acquireLock as ReturnType<typeof vi.fn>).mockImplementation(async () => {
378
340
  order.push('acquire');
379
341
  return { acquired: true, lock: { holderName: 'Alice' } };
380
342
  });
381
343
 
382
- await layer(workflow).writeFile('knowledge-base/Foo.md', 'after\n', undefined, async () => {
344
+ await layer(versionControl).writeFile('knowledge-base/Foo.md', 'after\n', undefined, async () => {
383
345
  order.push('check');
384
346
  // The check reads the state the write is about to replace.
385
347
  expect(await fs.readFile(path.join(root, 'knowledge-base/Foo.md'), 'utf-8')).toBe('before\n');
@@ -393,10 +355,10 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
393
355
  await fs.mkdir(path.join(root, 'knowledge-base'), { recursive: true });
394
356
  await fs.writeFile(path.join(root, 'knowledge-base/Foo.md'), 'theirs\n');
395
357
 
396
- const workflow = makeWorkflow();
358
+ const versionControl = makeVersionControl();
397
359
  const refusal = new Error('already exists');
398
360
  await expect(
399
- layer(workflow).writeFile('knowledge-base/Foo.md', 'mine\n', undefined, () => {
361
+ layer(versionControl).writeFile('knowledge-base/Foo.md', 'mine\n', undefined, () => {
400
362
  throw refusal;
401
363
  }),
402
364
  ).rejects.toBe(refusal);
@@ -404,30 +366,30 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
404
366
  expect(await fs.readFile(path.join(root, 'knowledge-base/Foo.md'), 'utf-8')).toBe('theirs\n');
405
367
  // Untouched, never no-commit: a discard would reset the path to HEAD and
406
368
  // destroy whatever save left those bytes there.
407
- expect(workflow.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Foo.md', USER);
408
- expect(workflow.releaseLockNoCommit).not.toHaveBeenCalled();
409
- expect(workflow.releaseLock).not.toHaveBeenCalled();
369
+ expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Foo.md', USER);
370
+ expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
371
+ expect(versionControl.releaseLock).not.toHaveBeenCalled();
410
372
  });
411
373
 
412
374
  it('rewriteFile reads, computes and writes with the lock HELD, from the bytes on disk at that moment', async () => {
413
375
  await fs.mkdir(path.join(root, 'knowledge-base'), { recursive: true });
414
376
  await fs.writeFile(path.join(root, 'knowledge-base/Foo.md'), 'owner: \n');
415
377
 
416
- const workflow = makeWorkflow();
378
+ const versionControl = makeVersionControl();
417
379
  const order: string[] = [];
418
- (workflow.acquireLock as ReturnType<typeof vi.fn>).mockImplementation(async () => {
380
+ (versionControl.acquireLock as ReturnType<typeof vi.fn>).mockImplementation(async () => {
419
381
  order.push('acquire');
420
382
  // Another writer lands while this call waits for the lock.
421
383
  await fs.writeFile(path.join(root, 'knowledge-base/Foo.md'), 'owner: alice\n');
422
384
  return { acquired: true, lock: { holderName: 'Alice' } };
423
385
  });
424
- (workflow.releaseLock as ReturnType<typeof vi.fn>).mockImplementation(async () => {
386
+ (versionControl.releaseLock as ReturnType<typeof vi.fn>).mockImplementation(async () => {
425
387
  order.push('release');
426
388
  return null;
427
389
  });
428
390
 
429
391
  let seen = '';
430
- await layer(workflow).rewriteFile('knowledge-base/Foo.md', (current) => {
392
+ await layer(versionControl).rewriteFile('knowledge-base/Foo.md', (current) => {
431
393
  order.push('rewrite');
432
394
  seen = current!.toString('utf8');
433
395
  return `${seen}note: kept\n`;
@@ -437,32 +399,32 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
437
399
  // It was handed what the other writer left, not what was there when the call began.
438
400
  expect(seen).toBe('owner: alice\n');
439
401
  expect(await fs.readFile(path.join(root, 'knowledge-base/Foo.md'), 'utf-8')).toBe('owner: alice\nnote: kept\n');
440
- expect(workflow.releaseLock).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Foo.md', USER);
402
+ expect(versionControl.releaseLock).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Foo.md', USER);
441
403
  });
442
404
 
443
405
  it('a rewriteFile that throws writes nothing, releases UNTOUCHED, and gives the caller its own error', async () => {
444
406
  await fs.mkdir(path.join(root, 'knowledge-base'), { recursive: true });
445
407
  await fs.writeFile(path.join(root, 'knowledge-base/Foo.md'), 'theirs\n');
446
408
 
447
- const workflow = makeWorkflow();
409
+ const versionControl = makeVersionControl();
448
410
  const refusal = new Error('old text is gone');
449
411
  await expect(
450
- layer(workflow).rewriteFile('knowledge-base/Foo.md', () => {
412
+ layer(versionControl).rewriteFile('knowledge-base/Foo.md', () => {
451
413
  throw refusal;
452
414
  }),
453
415
  ).rejects.toBe(refusal);
454
416
 
455
417
  expect(await fs.readFile(path.join(root, 'knowledge-base/Foo.md'), 'utf-8')).toBe('theirs\n');
456
- expect(workflow.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Foo.md', USER);
457
- expect(workflow.releaseLockNoCommit).not.toHaveBeenCalled();
458
- expect(workflow.releaseLock).not.toHaveBeenCalled();
418
+ expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Foo.md', USER);
419
+ expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
420
+ expect(versionControl.releaseLock).not.toHaveBeenCalled();
459
421
  });
460
422
 
461
423
  it('rewriteFile hands over null when nothing is at the path', async () => {
462
424
  await fs.mkdir(path.join(root, 'knowledge-base'), { recursive: true });
463
- const workflow = makeWorkflow();
425
+ const versionControl = makeVersionControl();
464
426
  let seen: Buffer | null | undefined;
465
- await layer(workflow).rewriteFile('knowledge-base/New.md', (current) => {
427
+ await layer(versionControl).rewriteFile('knowledge-base/New.md', (current) => {
466
428
  seen = current;
467
429
  return 'made\n';
468
430
  });
@@ -473,13 +435,13 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
473
435
  it('rewriteFile judges the bytes it is about to write with the pre-disk validator, under the lock', async () => {
474
436
  await fs.mkdir(path.join(root, 'knowledge-base'), { recursive: true });
475
437
  await fs.writeFile(path.join(root, 'knowledge-base/Foo.md'), 'ok\n');
476
- const workflow = makeWorkflow();
438
+ const versionControl = makeVersionControl();
477
439
  const refusal = new Error('refused by the validator');
478
440
  const judged: string[] = [];
479
441
  const guarded = new LockingFilesystem(
480
442
  { basePath: root, contained: true },
481
443
  {
482
- workflow,
444
+ versionControl,
483
445
  workspaceId: 'ws-feat',
484
446
  branch: 'feat',
485
447
  user: USER,
@@ -493,19 +455,19 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
493
455
  await expect(guarded.rewriteFile('knowledge-base/Foo.md', () => 'bad\n')).rejects.toBe(refusal);
494
456
  expect(judged).toEqual(['bad\n']);
495
457
  expect(await fs.readFile(path.join(root, 'knowledge-base/Foo.md'), 'utf-8')).toBe('ok\n');
496
- expect(workflow.releaseLockUntouched).toHaveBeenCalled();
458
+ expect(versionControl.releaseLockUntouched).toHaveBeenCalled();
497
459
  });
498
460
 
499
461
  it('rewriteFile refuses a path outside the repository before any lock', async () => {
500
- const workflow = makeWorkflow();
501
- await expect(layer(workflow).rewriteFile('elsewhere/Foo.md', () => 'x')).rejects.toThrow();
502
- expect(workflow.acquireLock).not.toHaveBeenCalled();
462
+ const versionControl = makeVersionControl();
463
+ await expect(layer(versionControl).rewriteFile('elsewhere/Foo.md', () => 'x')).rejects.toThrow();
464
+ expect(versionControl.acquireLock).not.toHaveBeenCalled();
503
465
  });
504
466
 
505
467
  it('writeFiles runs its check once EVERY lock is held, and lands only what the check keeps', async () => {
506
- const workflow = makeWorkflow();
468
+ const versionControl = makeVersionControl();
507
469
  const acquired: string[] = [];
508
- (workflow.acquireLock as ReturnType<typeof vi.fn>).mockImplementation(async (_w, _b, p: string) => {
470
+ (versionControl.acquireLock as ReturnType<typeof vi.fn>).mockImplementation(async (_w, _b, p: string) => {
509
471
  acquired.push(p);
510
472
  return { acquired: true, lock: { holderName: 'Alice' } };
511
473
  });
@@ -515,7 +477,7 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
515
477
  { path: 'knowledge-base/b.md', content: 'B' },
516
478
  { path: 'knowledge-base/c.md', content: 'C' },
517
479
  ];
518
- await layer(workflow).writeFiles(writes, 'batch', [], async (pending) => {
480
+ await layer(versionControl).writeFiles(writes, 'batch', [], async (pending) => {
519
481
  // Every path of the batch is locked before the check gets to judge any.
520
482
  expect([...acquired].sort()).toEqual(['knowledge-base/a.md', 'knowledge-base/b.md', 'knowledge-base/c.md']);
521
483
  expect(pending.map((w) => w.path)).toEqual(writes.map((w) => w.path));
@@ -528,16 +490,16 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
528
490
  // The dropped path stays OUT of the commit's scope — an unscoped commit
529
491
  // there would sweep in whatever another save left dirty on it — and
530
492
  // releases untouched, because this batch never wrote a byte to it.
531
- expect(workflow.commitChanges).toHaveBeenCalledWith('ws-feat', USER, 'batch', [
493
+ expect(versionControl.commitChanges).toHaveBeenCalledWith('ws-feat', USER, 'batch', [
532
494
  'knowledge-base/a.md',
533
495
  'knowledge-base/c.md',
534
496
  ]);
535
- expect(workflow.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/b.md', USER);
497
+ expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/b.md', USER);
536
498
  });
537
499
 
538
500
  it('a writeFiles check that drops EVERY write commits nothing and releases everything untouched', async () => {
539
- const workflow = makeWorkflow();
540
- const change = await layer(workflow).writeFiles(
501
+ const versionControl = makeVersionControl();
502
+ const change = await layer(versionControl).writeFiles(
541
503
  [{ path: 'knowledge-base/a.md', content: 'A' }],
542
504
  'batch',
543
505
  [],
@@ -546,15 +508,15 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
546
508
 
547
509
  expect(change).toBeNull();
548
510
  await expect(fs.access(path.join(root, 'knowledge-base/a.md'))).rejects.toThrow();
549
- expect(workflow.commitChanges).not.toHaveBeenCalled();
550
- expect(workflow.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/a.md', USER);
511
+ expect(versionControl.commitChanges).not.toHaveBeenCalled();
512
+ expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/a.md', USER);
551
513
  });
552
514
 
553
515
  it('a writeFiles check that THROWS refuses the whole batch, untouched', async () => {
554
- const workflow = makeWorkflow();
516
+ const versionControl = makeVersionControl();
555
517
  const refusal = new Error('nope');
556
518
  await expect(
557
- layer(workflow).writeFiles(
519
+ layer(versionControl).writeFiles(
558
520
  [{ path: 'knowledge-base/a.md', content: 'A' }, { path: 'knowledge-base/b.md', content: 'B' }],
559
521
  'batch',
560
522
  [],
@@ -565,8 +527,8 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
565
527
  ).rejects.toBe(refusal);
566
528
 
567
529
  await expect(fs.access(path.join(root, 'knowledge-base/a.md'))).rejects.toThrow();
568
- expect(workflow.commitChanges).not.toHaveBeenCalled();
569
- expect(workflow.releaseLockUntouched).toHaveBeenCalledTimes(2);
530
+ expect(versionControl.commitChanges).not.toHaveBeenCalled();
531
+ expect(versionControl.releaseLockUntouched).toHaveBeenCalledTimes(2);
570
532
  });
571
533
  });
572
534
 
@@ -589,11 +551,11 @@ describe('LockingFilesystem — creator read grants on creation', () => {
589
551
  }
590
552
 
591
553
  it('writeFile asks the planner about the new file and lands the content untouched', async () => {
592
- const workflow = makeWorkflow();
554
+ const versionControl = makeVersionControl();
593
555
  const creatorAccess = makeCreatorAccess();
594
556
  const fsLayer = new LockingFilesystem(
595
557
  { basePath: root, contained: true },
596
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
558
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
597
559
  );
598
560
  await fsLayer.writeFile('knowledge-base/KnowledgeBase/new.md', '# New\n');
599
561
  expect(creatorAccess.planForCreate).toHaveBeenCalledWith(
@@ -608,7 +570,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
608
570
  });
609
571
 
610
572
  it('writeFile seeds a subtree access.md (own lock cycle) before the file itself', async () => {
611
- const workflow = makeWorkflow();
573
+ const versionControl = makeVersionControl();
612
574
  const creatorAccess = makeCreatorAccess();
613
575
  creatorAccess.planForCreate.mockImplementation(async (_w, _u, p: string) =>
614
576
  p === 'knowledge-base/KnowledgeBase/Mine/doc.md'
@@ -622,10 +584,10 @@ describe('LockingFilesystem — creator read grants on creation', () => {
622
584
  );
623
585
  const fsLayer = new LockingFilesystem(
624
586
  { basePath: root, contained: true },
625
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
587
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
626
588
  );
627
589
  await fsLayer.writeFile('knowledge-base/KnowledgeBase/Mine/doc.md', 'body');
628
- const locked = (workflow.acquireLock as ReturnType<typeof vi.fn>).mock.calls.map(
590
+ const locked = (versionControl.acquireLock as ReturnType<typeof vi.fn>).mock.calls.map(
629
591
  (c) => c[2],
630
592
  );
631
593
  expect(locked).toEqual(['knowledge-base/KnowledgeBase/Mine/access.md', 'knowledge-base/KnowledgeBase/Mine/doc.md']);
@@ -638,7 +600,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
638
600
  });
639
601
 
640
602
  it('mkdir seeds the new folder access.md and then skips the .gitkeep (dir not empty)', async () => {
641
- const workflow = makeWorkflow();
603
+ const versionControl = makeVersionControl();
642
604
  const creatorAccess = makeCreatorAccess();
643
605
  creatorAccess.planForCreate.mockImplementation(async (_w, _u, p: string, kind: string) =>
644
606
  kind === 'dir'
@@ -651,7 +613,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
651
613
  );
652
614
  const fsLayer = new LockingFilesystem(
653
615
  { basePath: root, contained: true },
654
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
616
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
655
617
  );
656
618
  await fsLayer.mkdir('knowledge-base/KnowledgeBase/Projects');
657
619
  expect(
@@ -663,7 +625,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
663
625
  });
664
626
 
665
627
  it('writeFiles folds seeds into the same atomic batch, deduped across files', async () => {
666
- const workflow = makeWorkflow();
628
+ const versionControl = makeVersionControl();
667
629
  const creatorAccess = makeCreatorAccess();
668
630
  creatorAccess.planForCreate.mockResolvedValue({
669
631
  kind: 'seed-access-md',
@@ -672,7 +634,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
672
634
  });
673
635
  const fsLayer = new LockingFilesystem(
674
636
  { basePath: root, contained: true },
675
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
637
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
676
638
  );
677
639
  await fsLayer.writeFiles(
678
640
  [
@@ -685,8 +647,8 @@ describe('LockingFilesystem — creator read grants on creation', () => {
685
647
  expect(
686
648
  await fs.readFile(path.join(root, 'knowledge-base/KnowledgeBase/Mine/access.md'), 'utf-8'),
687
649
  ).toContain('Alice <alice@example.com>');
688
- expect(workflow.commitChanges).toHaveBeenCalledTimes(1);
689
- const locked = (workflow.acquireLock as ReturnType<typeof vi.fn>).mock.calls.map(
650
+ expect(versionControl.commitChanges).toHaveBeenCalledTimes(1);
651
+ const locked = (versionControl.acquireLock as ReturnType<typeof vi.fn>).mock.calls.map(
690
652
  (c) => c[2],
691
653
  );
692
654
  expect(locked).toEqual([
@@ -707,34 +669,31 @@ describe('LockingFilesystem — creator read grants on creation', () => {
707
669
  await fs.mkdir(path.join(root, 'knowledge-base/KnowledgeBase/Mine'), { recursive: true });
708
670
  const seedContent = '---\nread:\n - Alice <alice@example.com>\n---\n';
709
671
  await fs.writeFile(path.join(root, 'knowledge-base/KnowledgeBase/Mine/access.md'), seedContent);
710
- const workflow = makeWorkflow();
672
+ const versionControl = makeVersionControl();
711
673
  const creatorAccess = makeCreatorAccess();
712
674
  creatorAccess.planForCreate.mockResolvedValue({
713
675
  kind: 'seed-access-md',
714
676
  wsRelPath: 'knowledge-base/KnowledgeBase/Mine/access.md',
715
677
  apply: (current: string) => current, // grant already present → no-op
716
678
  });
717
- const emitted: { paths: string[] }[] = [];
718
679
  const fsLayer = new LockingFilesystem(
719
680
  { basePath: root, contained: true },
720
681
  {
721
- workflow,
682
+ versionControl,
722
683
  workspaceId: 'ws-feat',
723
684
  branch: 'feat',
724
685
  user: USER, kbDirName: KB,
725
686
  creatorAccess,
726
- fileChanges: { emit: (c: { paths: string[] }) => emitted.push(c) } as never,
727
687
  },
728
688
  );
729
689
  await fsLayer.writeFiles([{ path: 'knowledge-base/KnowledgeBase/Mine/doc.md', content: 'body' }], 'batch');
730
690
 
731
691
  // The seed's lock cycle still ran (acquired + released)...
732
- const locked = (workflow.acquireLock as ReturnType<typeof vi.fn>).mock.calls.map((c) => c[2]);
692
+ const locked = (versionControl.acquireLock as ReturnType<typeof vi.fn>).mock.calls.map((c) => c[2]);
733
693
  expect(locked).toContain('knowledge-base/KnowledgeBase/Mine/access.md');
734
694
  // ...but the commit is scoped to the caller's path only.
735
- const commitPaths = (workflow.commitChanges as ReturnType<typeof vi.fn>).mock.calls[0][3];
695
+ const commitPaths = (versionControl.commitChanges as ReturnType<typeof vi.fn>).mock.calls[0][3];
736
696
  expect(commitPaths).toEqual(['knowledge-base/KnowledgeBase/Mine/doc.md']);
737
- expect(emitted[0].paths).toEqual(['knowledge-base/KnowledgeBase/Mine/doc.md']);
738
697
  // The seeded file is untouched.
739
698
  expect(await fs.readFile(path.join(root, 'knowledge-base/KnowledgeBase/Mine/access.md'), 'utf-8')).toBe(
740
699
  seedContent,
@@ -756,7 +715,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
756
715
  await fs.mkdir(path.join(root, 'knowledge-base/KnowledgeBase/Mine'), { recursive: true });
757
716
  const priorSave = '---\nread:\n - Alice <alice@example.com>\n---\nprior queued bytes\n';
758
717
  await fs.writeFile(path.join(root, 'knowledge-base/KnowledgeBase/Mine/access.md'), priorSave);
759
- const workflow = makeWorkflow();
718
+ const versionControl = makeVersionControl();
760
719
  const creatorAccess = makeCreatorAccess();
761
720
  creatorAccess.planForCreate.mockResolvedValue({
762
721
  kind: 'seed-access-md',
@@ -765,21 +724,21 @@ describe('LockingFilesystem — creator read grants on creation', () => {
765
724
  });
766
725
  const fsLayer = new LockingFilesystem(
767
726
  { basePath: root, contained: true },
768
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
727
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
769
728
  );
770
729
  await fsLayer.writeFiles([{ path: 'knowledge-base/KnowledgeBase/Mine/doc.md', content: 'body' }], 'batch');
771
730
 
772
731
  // The batch's OWN committed path releases no-commit (clean — discard no-ops)...
773
- expect(workflow.releaseLockNoCommit).toHaveBeenCalledWith(
732
+ expect(versionControl.releaseLockNoCommit).toHaveBeenCalledWith(
774
733
  'ws-feat', 'feat', 'knowledge-base/KnowledgeBase/Mine/doc.md', USER,
775
734
  );
776
735
  // ...but the merely-locked seed path releases UNTOUCHED — never the
777
736
  // enqueue, never the discard.
778
- expect(workflow.releaseLockUntouched).toHaveBeenCalledWith(
737
+ expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith(
779
738
  'ws-feat', 'feat', 'knowledge-base/KnowledgeBase/Mine/access.md', USER,
780
739
  );
781
- expect(workflow.releaseLock).not.toHaveBeenCalled();
782
- expect(workflow.releaseLockNoCommit).not.toHaveBeenCalledWith(
740
+ expect(versionControl.releaseLock).not.toHaveBeenCalled();
741
+ expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalledWith(
783
742
  'ws-feat', 'feat', 'knowledge-base/KnowledgeBase/Mine/access.md', USER,
784
743
  );
785
744
  });
@@ -788,13 +747,13 @@ describe('LockingFilesystem — creator read grants on creation', () => {
788
747
  // First path acquires, second is contended: nothing has been written, so
789
748
  // the first path may hold ONLY someone else's still-queued bytes — the
790
749
  // unwind must neither discard them nor enqueue them under this user.
791
- const workflow = makeWorkflow();
792
- (workflow.acquireLock as ReturnType<typeof vi.fn>)
750
+ const versionControl = makeVersionControl();
751
+ (versionControl.acquireLock as ReturnType<typeof vi.fn>)
793
752
  .mockResolvedValueOnce({ acquired: true, lock: { holderUserId: 'user-1', holderName: 'Alice' } })
794
753
  .mockResolvedValue({ acquired: false, lock: { holderUserId: 'bob', holderName: 'Bob' } });
795
754
  const fsLayer = new LockingFilesystem(
796
755
  { basePath: root, contained: true },
797
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
756
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
798
757
  );
799
758
  await expect(
800
759
  fsLayer.writeFiles(
@@ -805,9 +764,9 @@ describe('LockingFilesystem — creator read grants on creation', () => {
805
764
  'batch',
806
765
  ),
807
766
  ).rejects.toThrow(/locked by Bob/);
808
- expect(workflow.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/A.md', USER);
809
- expect(workflow.releaseLock).not.toHaveBeenCalled();
810
- expect(workflow.releaseLockNoCommit).not.toHaveBeenCalled();
767
+ expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/A.md', USER);
768
+ expect(versionControl.releaseLock).not.toHaveBeenCalled();
769
+ expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
811
770
  }, 10_000);
812
771
 
813
772
  it('a DELETE batch whose later lock is contended deletes nothing — a folder never half-disappears', async () => {
@@ -817,13 +776,13 @@ describe('LockingFilesystem — creator read grants on creation', () => {
817
776
  await fs.mkdir(path.join(root, 'knowledge-base/Archive'), { recursive: true });
818
777
  await fs.writeFile(path.join(root, 'knowledge-base/Archive/a.md'), 'a');
819
778
  await fs.writeFile(path.join(root, 'knowledge-base/Archive/b.md'), 'b');
820
- const workflow = makeWorkflow();
821
- (workflow.acquireLock as ReturnType<typeof vi.fn>)
779
+ const versionControl = makeVersionControl();
780
+ (versionControl.acquireLock as ReturnType<typeof vi.fn>)
822
781
  .mockResolvedValueOnce({ acquired: true, lock: { holderUserId: 'user-1', holderName: 'Alice' } })
823
782
  .mockResolvedValue({ acquired: false, lock: { holderUserId: 'bob', holderName: 'Bob' } });
824
783
  const fsLayer = new LockingFilesystem(
825
784
  { basePath: root, contained: true },
826
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
785
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
827
786
  );
828
787
 
829
788
  await expect(
@@ -832,7 +791,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
832
791
 
833
792
  expect(await fs.readFile(path.join(root, 'knowledge-base/Archive/a.md'), 'utf-8')).toBe('a');
834
793
  expect(await fs.readFile(path.join(root, 'knowledge-base/Archive/b.md'), 'utf-8')).toBe('b');
835
- expect(workflow.commitChanges).not.toHaveBeenCalled();
794
+ expect(versionControl.commitChanges).not.toHaveBeenCalled();
836
795
  }, 10_000);
837
796
 
838
797
  it('a seed write that dies MID-WRITE restores the pre-image and releases UNTOUCHED', async () => {
@@ -845,7 +804,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
845
804
  const priorBytes = 'prior queued bytes\n';
846
805
  const seedPath = 'knowledge-base/KnowledgeBase/Mine/access.md';
847
806
  await fs.writeFile(path.join(root, seedPath), priorBytes);
848
- const workflow = makeWorkflow();
807
+ const versionControl = makeVersionControl();
849
808
  const creatorAccess = makeCreatorAccess();
850
809
  creatorAccess.planForCreate.mockResolvedValue({
851
810
  kind: 'seed-access-md',
@@ -854,7 +813,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
854
813
  });
855
814
  const fsLayer = new LockingFilesystem(
856
815
  { basePath: root, contained: true },
857
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
816
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
858
817
  );
859
818
  // Fail the SEED write once (simulating a mid-write death that left
860
819
  // partial bytes); every other write — including the restore — passes
@@ -879,13 +838,13 @@ describe('LockingFilesystem — creator read grants on creation', () => {
879
838
  // Pre-image restored — the partial bytes did not outlive the batch.
880
839
  expect(await fs.readFile(path.join(root, seedPath), 'utf-8')).toBe(priorBytes);
881
840
  // The failed seed stays OUT of the commit scope and releases untouched.
882
- const commitPaths = (workflow.commitChanges as ReturnType<typeof vi.fn>).mock.calls[0][3];
841
+ const commitPaths = (versionControl.commitChanges as ReturnType<typeof vi.fn>).mock.calls[0][3];
883
842
  expect(commitPaths).toEqual(['knowledge-base/KnowledgeBase/Mine/doc.md']);
884
- expect(workflow.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', seedPath, USER);
885
- expect(workflow.releaseLockNoCommit).not.toHaveBeenCalledWith(
843
+ expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', seedPath, USER);
844
+ expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalledWith(
886
845
  'ws-feat', 'feat', seedPath, USER,
887
846
  );
888
- expect(workflow.releaseLock).not.toHaveBeenCalled();
847
+ expect(versionControl.releaseLock).not.toHaveBeenCalled();
889
848
  });
890
849
 
891
850
  it('a LANDED seed rolls back to its pre-image when the batch commit fails — grant bytes must not outlive the batch uncommitted', async () => {
@@ -899,8 +858,8 @@ describe('LockingFilesystem — creator read grants on creation', () => {
899
858
  const priorBytes = 'prior queued bytes\n';
900
859
  const seedPath = 'knowledge-base/KnowledgeBase/Mine/access.md';
901
860
  await fs.writeFile(path.join(root, seedPath), priorBytes);
902
- const workflow = makeWorkflow();
903
- (workflow.commitChanges as ReturnType<typeof vi.fn>).mockRejectedValue(
861
+ const versionControl = makeVersionControl();
862
+ (versionControl.commitChanges as ReturnType<typeof vi.fn>).mockRejectedValue(
904
863
  new Error('commit exploded'),
905
864
  );
906
865
  const creatorAccess = makeCreatorAccess();
@@ -911,7 +870,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
911
870
  });
912
871
  const fsLayer = new LockingFilesystem(
913
872
  { basePath: root, contained: true },
914
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
873
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
915
874
  );
916
875
  await expect(
917
876
  fsLayer.writeFiles([{ path: 'knowledge-base/KnowledgeBase/Mine/doc.md', content: 'body' }], 'batch'),
@@ -920,11 +879,11 @@ describe('LockingFilesystem — creator read grants on creation', () => {
920
879
  expect(await fs.readFile(path.join(root, seedPath), 'utf-8')).toBe(priorBytes);
921
880
  // …and the seed releases UNTOUCHED (the prior queued bytes are not this
922
881
  // batch's to discard), while the caller's own write releases via discard.
923
- expect(workflow.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', seedPath, USER);
924
- expect(workflow.releaseLockNoCommit).toHaveBeenCalledWith(
882
+ expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', seedPath, USER);
883
+ expect(versionControl.releaseLockNoCommit).toHaveBeenCalledWith(
925
884
  'ws-feat', 'feat', 'knowledge-base/KnowledgeBase/Mine/doc.md', USER,
926
885
  );
927
- expect(workflow.releaseLockNoCommit).not.toHaveBeenCalledWith(
886
+ expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalledWith(
928
887
  'ws-feat', 'feat', seedPath, USER,
929
888
  );
930
889
  });
@@ -937,7 +896,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
937
896
  await fs.mkdir(path.join(root, 'knowledge-base/KnowledgeBase/Mine'), { recursive: true });
938
897
  const seedPath = 'knowledge-base/KnowledgeBase/Mine/access.md';
939
898
  await fs.writeFile(path.join(root, seedPath), 'prior\n');
940
- const workflow = makeWorkflow();
899
+ const versionControl = makeVersionControl();
941
900
  const creatorAccess = makeCreatorAccess();
942
901
  creatorAccess.planForCreate.mockResolvedValue({
943
902
  kind: 'seed-access-md',
@@ -946,7 +905,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
946
905
  });
947
906
  const fsLayer = new LockingFilesystem(
948
907
  { basePath: root, contained: true },
949
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
908
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
950
909
  );
951
910
  const original = LocalFilesystem.prototype.writeFile;
952
911
  const spy = vi
@@ -962,12 +921,12 @@ describe('LockingFilesystem — creator read grants on creation', () => {
962
921
  }
963
922
  // The unrecoverable seed path releases via the discard, everything else
964
923
  // untouched-or-no-commit as usual; the batch itself still succeeds.
965
- expect(workflow.releaseLockNoCommit).toHaveBeenCalledWith('ws-feat', 'feat', seedPath, USER);
966
- expect(workflow.releaseLockUntouched).not.toHaveBeenCalledWith(
924
+ expect(versionControl.releaseLockNoCommit).toHaveBeenCalledWith('ws-feat', 'feat', seedPath, USER);
925
+ expect(versionControl.releaseLockUntouched).not.toHaveBeenCalledWith(
967
926
  'ws-feat', 'feat', seedPath, USER,
968
927
  );
969
- expect(workflow.releaseLock).not.toHaveBeenCalled();
970
- const commitPaths = (workflow.commitChanges as ReturnType<typeof vi.fn>).mock.calls[0][3];
928
+ expect(versionControl.releaseLock).not.toHaveBeenCalled();
929
+ const commitPaths = (versionControl.commitChanges as ReturnType<typeof vi.fn>).mock.calls[0][3];
971
930
  expect(commitPaths).toEqual(['knowledge-base/KnowledgeBase/Mine/doc.md']);
972
931
  });
973
932
 
@@ -980,8 +939,8 @@ describe('LockingFilesystem — creator read grants on creation', () => {
980
939
  await fs.mkdir(path.join(root, 'knowledge-base/KnowledgeBase/Mine'), { recursive: true });
981
940
  const seedPath = 'knowledge-base/KnowledgeBase/Mine/access.md';
982
941
  await fs.writeFile(path.join(root, seedPath), 'grant already present\n');
983
- const workflow = makeWorkflow();
984
- (workflow.commitChanges as ReturnType<typeof vi.fn>).mockRejectedValue(
942
+ const versionControl = makeVersionControl();
943
+ (versionControl.commitChanges as ReturnType<typeof vi.fn>).mockRejectedValue(
985
944
  new PushNeedsAgentResolutionError('feat', '(batch)', 'non-fast-forward', 'rebase failed'),
986
945
  );
987
946
  const creatorAccess = makeCreatorAccess();
@@ -992,21 +951,21 @@ describe('LockingFilesystem — creator read grants on creation', () => {
992
951
  });
993
952
  const fsLayer = new LockingFilesystem(
994
953
  { basePath: root, contained: true },
995
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
954
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
996
955
  );
997
956
  await expect(
998
957
  fsLayer.writeFiles([{ path: 'knowledge-base/KnowledgeBase/Mine/doc.md', content: 'body' }], 'batch'),
999
958
  ).rejects.toBeInstanceOf(PushNeedsAgentResolutionError);
1000
- expect(workflow.releaseLock).toHaveBeenCalledWith(
959
+ expect(versionControl.releaseLock).toHaveBeenCalledWith(
1001
960
  'ws-feat', 'feat', 'knowledge-base/KnowledgeBase/Mine/doc.md', USER,
1002
961
  );
1003
- expect(workflow.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', seedPath, USER);
1004
- expect(workflow.releaseLock).not.toHaveBeenCalledWith('ws-feat', 'feat', seedPath, USER);
1005
- expect(workflow.releaseLockNoCommit).not.toHaveBeenCalled();
962
+ expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', seedPath, USER);
963
+ expect(versionControl.releaseLock).not.toHaveBeenCalledWith('ws-feat', 'feat', seedPath, USER);
964
+ expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
1006
965
  });
1007
966
 
1008
967
  it('a LANDED seed rides the commit scope with the caller paths', async () => {
1009
- const workflow = makeWorkflow();
968
+ const versionControl = makeVersionControl();
1010
969
  const creatorAccess = makeCreatorAccess();
1011
970
  creatorAccess.planForCreate.mockResolvedValue({
1012
971
  kind: 'seed-access-md',
@@ -1015,20 +974,20 @@ describe('LockingFilesystem — creator read grants on creation', () => {
1015
974
  });
1016
975
  const fsLayer = new LockingFilesystem(
1017
976
  { basePath: root, contained: true },
1018
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
977
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
1019
978
  );
1020
979
  await fsLayer.writeFiles([{ path: 'knowledge-base/KnowledgeBase/Mine/doc.md', content: 'body' }], 'batch');
1021
- const commitPaths = (workflow.commitChanges as ReturnType<typeof vi.fn>).mock.calls[0][3];
980
+ const commitPaths = (versionControl.commitChanges as ReturnType<typeof vi.fn>).mock.calls[0][3];
1022
981
  expect(commitPaths).toEqual(['knowledge-base/KnowledgeBase/Mine/doc.md', 'knowledge-base/KnowledgeBase/Mine/access.md']);
1023
982
  });
1024
983
 
1025
984
  it('a planner failure never blocks the write', async () => {
1026
- const workflow = makeWorkflow();
985
+ const versionControl = makeVersionControl();
1027
986
  const creatorAccess = makeCreatorAccess();
1028
987
  creatorAccess.planForCreate.mockRejectedValue(new Error('planner down'));
1029
988
  const fsLayer = new LockingFilesystem(
1030
989
  { basePath: root, contained: true },
1031
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
990
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
1032
991
  );
1033
992
  await fsLayer.writeFile('knowledge-base/KnowledgeBase/new.md', 'x');
1034
993
  expect(await fs.readFile(path.join(root, 'knowledge-base/KnowledgeBase/new.md'), 'utf-8')).toBe('x');
@@ -1064,50 +1023,50 @@ describe('LockingFilesystem refuses to create anything outside the repository fo
1064
1023
  };
1065
1024
  }
1066
1025
 
1067
- function layer(workflow: IWorkflowService) {
1026
+ function layer(versionControl: IVersionControlService) {
1068
1027
  const creatorAccess = makeCreatorAccess();
1069
1028
  const validateWrite = vi.fn();
1070
1029
  const fsLayer = new LockingFilesystem(
1071
1030
  { basePath: root, contained: true },
1072
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess, validateWrite },
1031
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess, validateWrite },
1073
1032
  );
1074
1033
  return { fsLayer, creatorAccess, validateWrite };
1075
1034
  }
1076
1035
 
1077
1036
  async function expectRefused(
1078
1037
  op: Promise<unknown>,
1079
- workflow: IWorkflowService,
1038
+ versionControl: IVersionControlService,
1080
1039
  creatorAccess: ReturnType<typeof makeCreatorAccess>,
1081
1040
  validateWrite: ReturnType<typeof vi.fn>,
1082
1041
  ): Promise<void> {
1083
- await expect(op).rejects.toBeInstanceOf(WorkflowValidationError);
1042
+ await expect(op).rejects.toBeInstanceOf(VersionControlValidationError);
1084
1043
  // The corrected path is spelled out, under the clone folder.
1085
1044
  await expect(op).rejects.toThrow('"knowledge-base/KnowledgeBase/Reviews');
1086
1045
  // Refused before any side effect.
1087
- expect(workflow.acquireLock).not.toHaveBeenCalled();
1088
- expect(workflow.releaseLock).not.toHaveBeenCalled();
1089
- expect(workflow.releaseLockNoCommit).not.toHaveBeenCalled();
1090
- expect(workflow.commitChanges).not.toHaveBeenCalled();
1046
+ expect(versionControl.acquireLock).not.toHaveBeenCalled();
1047
+ expect(versionControl.releaseLock).not.toHaveBeenCalled();
1048
+ expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
1049
+ expect(versionControl.commitChanges).not.toHaveBeenCalled();
1091
1050
  expect(creatorAccess.planForCreate).not.toHaveBeenCalled();
1092
1051
  expect(validateWrite).not.toHaveBeenCalled();
1093
1052
  await expect(fs.access(path.join(root, 'KnowledgeBase'))).rejects.toBeDefined();
1094
1053
  }
1095
1054
 
1096
1055
  it('writeFile', async () => {
1097
- const workflow = makeWorkflow();
1098
- const { fsLayer, creatorAccess, validateWrite } = layer(workflow);
1099
- await expectRefused(fsLayer.writeFile(STRAY, '<p>review</p>'), workflow, creatorAccess, validateWrite);
1056
+ const versionControl = makeVersionControl();
1057
+ const { fsLayer, creatorAccess, validateWrite } = layer(versionControl);
1058
+ await expectRefused(fsLayer.writeFile(STRAY, '<p>review</p>'), versionControl, creatorAccess, validateWrite);
1100
1059
  });
1101
1060
 
1102
1061
  it('appendFile', async () => {
1103
- const workflow = makeWorkflow();
1104
- const { fsLayer, creatorAccess, validateWrite } = layer(workflow);
1105
- await expectRefused(fsLayer.appendFile('KnowledgeBase/Reviews/review.log', 'line\n'), workflow, creatorAccess, validateWrite);
1062
+ const versionControl = makeVersionControl();
1063
+ const { fsLayer, creatorAccess, validateWrite } = layer(versionControl);
1064
+ await expectRefused(fsLayer.appendFile('KnowledgeBase/Reviews/review.log', 'line\n'), versionControl, creatorAccess, validateWrite);
1106
1065
  });
1107
1066
 
1108
1067
  it('writeFiles refuses the whole batch when one path is outside: nothing written, nothing locked', async () => {
1109
- const workflow = makeWorkflow();
1110
- const { fsLayer, creatorAccess, validateWrite } = layer(workflow);
1068
+ const versionControl = makeVersionControl();
1069
+ const { fsLayer, creatorAccess, validateWrite } = layer(versionControl);
1111
1070
  await expectRefused(
1112
1071
  fsLayer.writeFiles(
1113
1072
  [
@@ -1116,7 +1075,7 @@ describe('LockingFilesystem refuses to create anything outside the repository fo
1116
1075
  ],
1117
1076
  'batch',
1118
1077
  ),
1119
- workflow,
1078
+ versionControl,
1120
1079
  creatorAccess,
1121
1080
  validateWrite,
1122
1081
  );
@@ -1124,19 +1083,19 @@ describe('LockingFilesystem refuses to create anything outside the repository fo
1124
1083
  });
1125
1084
 
1126
1085
  it('mkdir', async () => {
1127
- const workflow = makeWorkflow();
1128
- const { fsLayer, creatorAccess, validateWrite } = layer(workflow);
1129
- await expectRefused(fsLayer.mkdir('KnowledgeBase/Reviews'), workflow, creatorAccess, validateWrite);
1086
+ const versionControl = makeVersionControl();
1087
+ const { fsLayer, creatorAccess, validateWrite } = layer(versionControl);
1088
+ await expectRefused(fsLayer.mkdir('KnowledgeBase/Reviews'), versionControl, creatorAccess, validateWrite);
1130
1089
  });
1131
1090
 
1132
1091
  it('moveFile refuses a destination outside the repository and leaves the source in place', async () => {
1133
1092
  await fs.mkdir(path.join(root, 'knowledge-base/KnowledgeBase'), { recursive: true });
1134
1093
  await fs.writeFile(path.join(root, 'knowledge-base/KnowledgeBase/PR-12.html'), 'x');
1135
- const workflow = makeWorkflow();
1136
- const { fsLayer, creatorAccess, validateWrite } = layer(workflow);
1094
+ const versionControl = makeVersionControl();
1095
+ const { fsLayer, creatorAccess, validateWrite } = layer(versionControl);
1137
1096
  await expectRefused(
1138
1097
  fsLayer.moveFile('knowledge-base/KnowledgeBase/PR-12.html', STRAY),
1139
- workflow,
1098
+ versionControl,
1140
1099
  creatorAccess,
1141
1100
  validateWrite,
1142
1101
  );
@@ -1146,11 +1105,11 @@ describe('LockingFilesystem refuses to create anything outside the repository fo
1146
1105
  it('copyFile refuses a destination outside the repository', async () => {
1147
1106
  await fs.mkdir(path.join(root, 'knowledge-base/KnowledgeBase'), { recursive: true });
1148
1107
  await fs.writeFile(path.join(root, 'knowledge-base/KnowledgeBase/PR-12.html'), 'x');
1149
- const workflow = makeWorkflow();
1150
- const { fsLayer, creatorAccess, validateWrite } = layer(workflow);
1108
+ const versionControl = makeVersionControl();
1109
+ const { fsLayer, creatorAccess, validateWrite } = layer(versionControl);
1151
1110
  await expectRefused(
1152
1111
  fsLayer.copyFile('knowledge-base/KnowledgeBase/PR-12.html', STRAY),
1153
- workflow,
1112
+ versionControl,
1154
1113
  creatorAccess,
1155
1114
  validateWrite,
1156
1115
  );
@@ -1160,20 +1119,20 @@ describe('LockingFilesystem refuses to create anything outside the repository fo
1160
1119
  // Containment is checked against the WORKSPACE dir, so the underlying
1161
1120
  // filesystem would happily resolve `knowledge-base/../stray.md` to a file
1162
1121
  // next to the clone. The prefix alone is not proof of being inside.
1163
- const workflow = makeWorkflow();
1164
- const { fsLayer, creatorAccess, validateWrite } = layer(workflow);
1165
- await expect(fsLayer.writeFile('knowledge-base/../stray.md', 'x')).rejects.toBeInstanceOf(WorkflowValidationError);
1166
- expect(workflow.acquireLock).not.toHaveBeenCalled();
1122
+ const versionControl = makeVersionControl();
1123
+ const { fsLayer, creatorAccess, validateWrite } = layer(versionControl);
1124
+ await expect(fsLayer.writeFile('knowledge-base/../stray.md', 'x')).rejects.toBeInstanceOf(VersionControlValidationError);
1125
+ expect(versionControl.acquireLock).not.toHaveBeenCalled();
1167
1126
  expect(creatorAccess.planForCreate).not.toHaveBeenCalled();
1168
1127
  expect(validateWrite).not.toHaveBeenCalled();
1169
1128
  await expect(fs.access(path.join(root, 'stray.md'))).rejects.toBeDefined();
1170
1129
  });
1171
1130
 
1172
1131
  it('the folder is matched as a whole segment: `knowledge-based/x.md` is outside too', async () => {
1173
- const workflow = makeWorkflow();
1174
- const { fsLayer } = layer(workflow);
1175
- await expect(fsLayer.writeFile('knowledge-based/x.md', 'x')).rejects.toBeInstanceOf(WorkflowValidationError);
1176
- expect(workflow.acquireLock).not.toHaveBeenCalled();
1132
+ const versionControl = makeVersionControl();
1133
+ const { fsLayer } = layer(versionControl);
1134
+ await expect(fsLayer.writeFile('knowledge-based/x.md', 'x')).rejects.toBeInstanceOf(VersionControlValidationError);
1135
+ expect(versionControl.acquireLock).not.toHaveBeenCalled();
1177
1136
  });
1178
1137
 
1179
1138
  it('deleteFile still removes a file the old behaviour left beside the repository', async () => {
@@ -1181,8 +1140,8 @@ describe('LockingFilesystem refuses to create anything outside the repository fo
1181
1140
  // created before this guard existed must be able to clean it up.
1182
1141
  await fs.mkdir(path.join(root, 'KnowledgeBase/Reviews'), { recursive: true });
1183
1142
  await fs.writeFile(path.join(root, STRAY), 'stray');
1184
- const workflow = makeWorkflow();
1185
- const { fsLayer } = layer(workflow);
1143
+ const versionControl = makeVersionControl();
1144
+ const { fsLayer } = layer(versionControl);
1186
1145
  await fsLayer.deleteFile(STRAY);
1187
1146
  await expect(fs.access(path.join(root, STRAY))).rejects.toBeDefined();
1188
1147
  });
@@ -1191,8 +1150,8 @@ describe('LockingFilesystem refuses to create anything outside the repository fo
1191
1150
  await fs.mkdir(path.join(root, 'KnowledgeBase/Reviews'), { recursive: true });
1192
1151
  await fs.writeFile(path.join(root, STRAY), 'stray');
1193
1152
  await fs.mkdir(path.join(root, 'knowledge-base'), { recursive: true });
1194
- const workflow = makeWorkflow();
1195
- const { fsLayer } = layer(workflow);
1153
+ const versionControl = makeVersionControl();
1154
+ const { fsLayer } = layer(versionControl);
1196
1155
  await fsLayer.moveFile(STRAY, CORRECTED);
1197
1156
  expect(await fs.readFile(path.join(root, CORRECTED), 'utf-8')).toBe('stray');
1198
1157
  await expect(fs.access(path.join(root, STRAY))).rejects.toBeDefined();
@@ -1217,85 +1176,85 @@ describe('LockingFilesystem — the agent roles.yaml gate covers every op that l
1217
1176
  await fs.rm(root, { recursive: true, force: true });
1218
1177
  });
1219
1178
 
1220
- function layer(workflow: IWorkflowService) {
1179
+ function layer(versionControl: IVersionControlService) {
1221
1180
  const validateWrite = makeAgentRolesYamlWriteValidator(KB, () =>
1222
1181
  fs.readFile(path.join(root, ROLES), 'utf-8').catch(() => null),
1223
1182
  );
1224
1183
  return new LockingFilesystem(
1225
1184
  { basePath: root, contained: true },
1226
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, validateWrite },
1185
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, validateWrite },
1227
1186
  );
1228
1187
  }
1229
1188
 
1230
- async function expectRefused(op: Promise<unknown>, workflow: IWorkflowService): Promise<void> {
1231
- await expect(op).rejects.toBeInstanceOf(RolesYamlNewRoleError);
1232
- expect(workflow.acquireLock).not.toHaveBeenCalled();
1189
+ async function expectRefused(op: Promise<unknown>, versionControl: IVersionControlService): Promise<void> {
1190
+ await expect(op).rejects.toMatchObject({ status: 422, payload: { kind: 'roles-yaml-new-role' } });
1191
+ expect(versionControl.acquireLock).not.toHaveBeenCalled();
1233
1192
  expect(await fs.readFile(path.join(root, ROLES), 'utf-8')).toBe(CURRENT);
1234
1193
  }
1235
1194
 
1236
1195
  it('writeFile', async () => {
1237
- const workflow = makeWorkflow();
1238
- await expectRefused(layer(workflow).writeFile(ROLES, CURRENT + NEW_ROLE), workflow);
1196
+ const versionControl = makeVersionControl();
1197
+ await expectRefused(layer(versionControl).writeFile(ROLES, CURRENT + NEW_ROLE), versionControl);
1239
1198
  });
1240
1199
 
1241
1200
  it('writeFiles refuses the whole batch', async () => {
1242
- const workflow = makeWorkflow();
1201
+ const versionControl = makeVersionControl();
1243
1202
  await expectRefused(
1244
- layer(workflow).writeFiles([{ path: `${KB}/KnowledgeBase/ok.md`, content: 'fine' }, { path: ROLES, content: CURRENT + NEW_ROLE }], 'batch'),
1245
- workflow,
1203
+ layer(versionControl).writeFiles([{ path: `${KB}/KnowledgeBase/ok.md`, content: 'fine' }, { path: ROLES, content: CURRENT + NEW_ROLE }], 'batch'),
1204
+ versionControl,
1246
1205
  );
1247
1206
  await expect(fs.access(path.join(root, KB, 'KnowledgeBase/ok.md'))).rejects.toBeDefined();
1248
1207
  });
1249
1208
 
1250
1209
  it('appendFile, judged by the file it would leave', async () => {
1251
- const workflow = makeWorkflow();
1252
- await expectRefused(layer(workflow).appendFile(ROLES, NEW_ROLE), workflow);
1210
+ const versionControl = makeVersionControl();
1211
+ await expectRefused(layer(versionControl).appendFile(ROLES, NEW_ROLE), versionControl);
1253
1212
  });
1254
1213
 
1255
1214
  it('copyFile, judged by the source bytes', async () => {
1256
- const workflow = makeWorkflow();
1257
- await expectRefused(layer(workflow).copyFile(DRAFT, ROLES, { overwrite: true }), workflow);
1215
+ const versionControl = makeVersionControl();
1216
+ await expectRefused(layer(versionControl).copyFile(DRAFT, ROLES, { overwrite: true }), versionControl);
1258
1217
  });
1259
1218
 
1260
1219
  it('moveFile, leaving the source where it was', async () => {
1261
- const workflow = makeWorkflow();
1262
- await expectRefused(layer(workflow).moveFile(DRAFT, ROLES, { overwrite: true }), workflow);
1220
+ const versionControl = makeVersionControl();
1221
+ await expectRefused(layer(versionControl).moveFile(DRAFT, ROLES, { overwrite: true }), versionControl);
1263
1222
  expect(await fs.readFile(path.join(root, DRAFT), 'utf-8')).toBe(CURRENT + NEW_ROLE);
1264
1223
  });
1265
1224
 
1266
1225
  it('a members-only write and append land as before', async () => {
1267
- const workflow = makeWorkflow();
1268
- const fsLayer = layer(workflow);
1226
+ const versionControl = makeVersionControl();
1227
+ const fsLayer = layer(versionControl);
1269
1228
  await fsLayer.writeFile(ROLES, `${CURRENT} - b@x.eu\n`);
1270
1229
  await fsLayer.appendFile(ROLES, ' - c@x.eu\n');
1271
1230
  expect(await fs.readFile(path.join(root, ROLES), 'utf-8')).toBe(`${CURRENT} - b@x.eu\n - c@x.eu\n`);
1272
- expect(workflow.releaseLock).toHaveBeenCalledTimes(2);
1231
+ expect(versionControl.releaseLock).toHaveBeenCalledTimes(2);
1273
1232
  });
1274
1233
 
1275
1234
  describe('judged again under the lock, against what a concurrent writer left there', () => {
1276
1235
  const WITH_PHOENIX = CURRENT + NEW_ROLE;
1277
1236
 
1278
- /** A workflow whose acquire lets `race` rewrite disk first — the lock holder that finished just before us. */
1279
- function racingWorkflow(race: () => Promise<void>): IWorkflowService {
1280
- const workflow = makeWorkflow();
1281
- const acquired = (workflow.acquireLock as ReturnType<typeof vi.fn>).getMockImplementation()!;
1237
+ /** A version-control service whose acquire lets `race` rewrite disk first — the lock holder that finished just before us. */
1238
+ function racingVersionControl(race: () => Promise<void>): IVersionControlService {
1239
+ const versionControl = makeVersionControl();
1240
+ const acquired = (versionControl.acquireLock as ReturnType<typeof vi.fn>).getMockImplementation()!;
1282
1241
  let raced = false;
1283
- (workflow.acquireLock as ReturnType<typeof vi.fn>).mockImplementation(async (...args: unknown[]) => {
1242
+ (versionControl.acquireLock as ReturnType<typeof vi.fn>).mockImplementation(async (...args: unknown[]) => {
1284
1243
  if (!raced) {
1285
1244
  raced = true;
1286
1245
  await race();
1287
1246
  }
1288
1247
  return acquired(...args);
1289
1248
  });
1290
- return workflow;
1249
+ return versionControl;
1291
1250
  }
1292
1251
 
1293
- async function expectRefusedUnderLock(op: Promise<unknown>, workflow: IWorkflowService, locks: number): Promise<void> {
1294
- await expect(op).rejects.toBeInstanceOf(RolesYamlNewRoleError);
1295
- expect(workflow.releaseLockUntouched).toHaveBeenCalledTimes(locks);
1296
- expect(workflow.releaseLockNoCommit).not.toHaveBeenCalled();
1297
- expect(workflow.releaseLock).not.toHaveBeenCalled();
1298
- expect(workflow.commitChanges).not.toHaveBeenCalled();
1252
+ async function expectRefusedUnderLock(op: Promise<unknown>, versionControl: IVersionControlService, locks: number): Promise<void> {
1253
+ await expect(op).rejects.toMatchObject({ status: 422, payload: { kind: 'roles-yaml-new-role' } });
1254
+ expect(versionControl.releaseLockUntouched).toHaveBeenCalledTimes(locks);
1255
+ expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
1256
+ expect(versionControl.releaseLock).not.toHaveBeenCalled();
1257
+ expect(versionControl.commitChanges).not.toHaveBeenCalled();
1299
1258
  expect(await fs.readFile(path.join(root, ROLES), 'utf-8')).toBe(CURRENT);
1300
1259
  }
1301
1260
 
@@ -1306,15 +1265,15 @@ describe('LockingFilesystem — the agent roles.yaml gate covers every op that l
1306
1265
  });
1307
1266
 
1308
1267
  it('writeFile cannot reinstate a role deleted while it waited for the lock', async () => {
1309
- const workflow = racingWorkflow(deletePhoenix);
1310
- await expectRefusedUnderLock(layer(workflow).writeFile(ROLES, `${WITH_PHOENIX} - q@x.eu\n`), workflow, 1);
1268
+ const versionControl = racingVersionControl(deletePhoenix);
1269
+ await expectRefusedUnderLock(layer(versionControl).writeFile(ROLES, `${WITH_PHOENIX} - q@x.eu\n`), versionControl, 1);
1311
1270
  });
1312
1271
 
1313
1272
  it('writeFiles re-judges the whole batch once every lock is held', async () => {
1314
- const workflow = racingWorkflow(deletePhoenix);
1273
+ const versionControl = racingVersionControl(deletePhoenix);
1315
1274
  await expectRefusedUnderLock(
1316
- layer(workflow).writeFiles([{ path: `${KB}/KnowledgeBase/ok.md`, content: 'fine' }, { path: ROLES, content: WITH_PHOENIX }], 'batch'),
1317
- workflow,
1275
+ layer(versionControl).writeFiles([{ path: `${KB}/KnowledgeBase/ok.md`, content: 'fine' }, { path: ROLES, content: WITH_PHOENIX }], 'batch'),
1276
+ versionControl,
1318
1277
  2,
1319
1278
  );
1320
1279
  await expect(fs.access(path.join(root, KB, 'KnowledgeBase/ok.md'))).rejects.toBeDefined();
@@ -1324,31 +1283,31 @@ describe('LockingFilesystem — the agent roles.yaml gate covers every op that l
1324
1283
  // Appending never adds a role the file had before, so what a race can
1325
1284
  // change is validity: the same line appended to the rewritten file breaks it.
1326
1285
  const rewritten = 'roles:\n Admin:\n - a@x.eu';
1327
- const workflow = racingWorkflow(() => fs.writeFile(path.join(root, ROLES), rewritten));
1328
- await expect(layer(workflow).appendFile(ROLES, ' - b@x.eu\n')).rejects.toBeInstanceOf(RolesYamlInvalidError);
1329
- expect(workflow.releaseLockUntouched).toHaveBeenCalledTimes(1);
1330
- expect(workflow.releaseLockNoCommit).not.toHaveBeenCalled();
1286
+ const versionControl = racingVersionControl(() => fs.writeFile(path.join(root, ROLES), rewritten));
1287
+ await expect(layer(versionControl).appendFile(ROLES, ' - b@x.eu\n')).rejects.toMatchObject({ status: 422, payload: { kind: 'roles-yaml-invalid' } });
1288
+ expect(versionControl.releaseLockUntouched).toHaveBeenCalledTimes(1);
1289
+ expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
1331
1290
  expect(await fs.readFile(path.join(root, ROLES), 'utf-8')).toBe(rewritten);
1332
1291
  });
1333
1292
 
1334
1293
  it('copyFile judges — and writes — the source bytes read under the lock', async () => {
1335
1294
  await fs.writeFile(path.join(root, DRAFT), WITH_PHOENIX);
1336
- const workflow = racingWorkflow(deletePhoenix);
1337
- await expectRefusedUnderLock(layer(workflow).copyFile(DRAFT, ROLES, { overwrite: true }), workflow, 1);
1295
+ const versionControl = racingVersionControl(deletePhoenix);
1296
+ await expectRefusedUnderLock(layer(versionControl).copyFile(DRAFT, ROLES, { overwrite: true }), versionControl, 1);
1338
1297
  });
1339
1298
 
1340
1299
  it('moveFile re-judges with both locks held, and both release untouched', async () => {
1341
1300
  await fs.writeFile(path.join(root, DRAFT), WITH_PHOENIX);
1342
- const workflow = racingWorkflow(deletePhoenix);
1343
- await expectRefusedUnderLock(layer(workflow).moveFile(DRAFT, ROLES, { overwrite: true }), workflow, 2);
1301
+ const versionControl = racingVersionControl(deletePhoenix);
1302
+ await expectRefusedUnderLock(layer(versionControl).moveFile(DRAFT, ROLES, { overwrite: true }), versionControl, 2);
1344
1303
  expect(await fs.readFile(path.join(root, DRAFT), 'utf-8')).toBe(WITH_PHOENIX);
1345
1304
  });
1346
1305
 
1347
1306
  it('a members-only write whose roles all survive the wait still lands', async () => {
1348
- const workflow = racingWorkflow(() => fs.writeFile(path.join(root, ROLES), `${WITH_PHOENIX} - q@x.eu\n`));
1349
- await layer(workflow).writeFile(ROLES, `${CURRENT} - b@x.eu\n`);
1307
+ const versionControl = racingVersionControl(() => fs.writeFile(path.join(root, ROLES), `${WITH_PHOENIX} - q@x.eu\n`));
1308
+ await layer(versionControl).writeFile(ROLES, `${CURRENT} - b@x.eu\n`);
1350
1309
  expect(await fs.readFile(path.join(root, ROLES), 'utf-8')).toBe(`${CURRENT} - b@x.eu\n`);
1351
- expect(workflow.releaseLock).toHaveBeenCalledTimes(1);
1310
+ expect(versionControl.releaseLock).toHaveBeenCalledTimes(1);
1352
1311
  });
1353
1312
 
1354
1313
  /** A validator that passes before the lock and refuses under it by throwing a bare string. */
@@ -1363,36 +1322,36 @@ describe('LockingFilesystem — the agent roles.yaml gate covers every op that l
1363
1322
  }
1364
1323
 
1365
1324
  it('a check that throws a non-Error still releases untouched, and the caller gets that value', async () => {
1366
- const workflow = makeWorkflow();
1325
+ const versionControl = makeVersionControl();
1367
1326
  const fsLayer = new LockingFilesystem(
1368
1327
  { basePath: root, contained: true },
1369
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, validateWrite: refusesUnderLockWith('refused') },
1328
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, validateWrite: refusesUnderLockWith('refused') },
1370
1329
  );
1371
1330
  await expect(fsLayer.writeFile(ROLES, CURRENT)).rejects.toBe('refused');
1372
- expect(workflow.releaseLockUntouched).toHaveBeenCalledTimes(1);
1373
- expect(workflow.releaseLockNoCommit).not.toHaveBeenCalled();
1331
+ expect(versionControl.releaseLockUntouched).toHaveBeenCalledTimes(1);
1332
+ expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
1374
1333
  expect(await fs.readFile(path.join(root, ROLES), 'utf-8')).toBe(WITH_PHOENIX);
1375
1334
  });
1376
1335
 
1377
1336
  it('a move whose check throws a non-Error releases BOTH locks untouched', async () => {
1378
- const workflow = makeWorkflow();
1337
+ const versionControl = makeVersionControl();
1379
1338
  const fsLayer = new LockingFilesystem(
1380
1339
  { basePath: root, contained: true },
1381
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, validateWrite: refusesUnderLockWith('refused') },
1340
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, validateWrite: refusesUnderLockWith('refused') },
1382
1341
  );
1383
1342
  await expect(fsLayer.moveFile(DRAFT, ROLES, { overwrite: true })).rejects.toBe('refused');
1384
- expect(workflow.releaseLockUntouched).toHaveBeenCalledTimes(2);
1385
- expect(workflow.releaseLockNoCommit).not.toHaveBeenCalled();
1343
+ expect(versionControl.releaseLockUntouched).toHaveBeenCalledTimes(2);
1344
+ expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
1386
1345
  expect(await fs.readFile(path.join(root, DRAFT), 'utf-8')).toBe(WITH_PHOENIX);
1387
1346
  });
1388
1347
  });
1389
1348
 
1390
1349
  it('a copy elsewhere never reads its source for a validator that does not claim the destination', async () => {
1391
- const workflow = makeWorkflow();
1350
+ const versionControl = makeVersionControl();
1392
1351
  const validateWrite = Object.assign(vi.fn(), { appliesTo: vi.fn(() => false) });
1393
1352
  const fsLayer = new LockingFilesystem(
1394
1353
  { basePath: root, contained: true },
1395
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, validateWrite },
1354
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, validateWrite },
1396
1355
  );
1397
1356
  await fsLayer.copyFile(DRAFT, `${KB}/KnowledgeBase/copy.yaml`);
1398
1357
  expect(validateWrite.appliesTo).toHaveBeenCalledWith(`${KB}/KnowledgeBase/copy.yaml`);
@@ -1421,7 +1380,7 @@ describe('LockingFilesystem — a destination that is taken is refused, never re
1421
1380
  await fs.writeFile(onDisk(SOURCE), 'source');
1422
1381
  fsLayer = new LockingFilesystem(
1423
1382
  { basePath: root, contained: true },
1424
- { workflow: makeWorkflow(), workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
1383
+ { versionControl: makeVersionControl(), workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
1425
1384
  );
1426
1385
  });
1427
1386
 
@@ -1463,10 +1422,10 @@ describe('LockingFilesystem — a destination that is taken is refused, never re
1463
1422
  * every lock it unwinds releases untouched.
1464
1423
  */
1465
1424
  it('releases UNTOUCHED when the destination is taken, so a lost race cannot eat the winner', async () => {
1466
- const workflow = makeWorkflow();
1425
+ const versionControl = makeVersionControl();
1467
1426
  const layer = new LockingFilesystem(
1468
1427
  { basePath: root, contained: true },
1469
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
1428
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
1470
1429
  );
1471
1430
  await fs.writeFile(onDisk(TAKEN), 'the winner of the race');
1472
1431
 
@@ -1475,10 +1434,10 @@ describe('LockingFilesystem — a destination that is taken is refused, never re
1475
1434
  // Both ends of the move: the destination is the one holding the winner's
1476
1435
  // bytes, and the source was not written either.
1477
1436
  for (const p of [SOURCE, TAKEN]) {
1478
- expect(workflow.releaseLockUntouched, p).toHaveBeenCalledWith('ws-feat', 'feat', p, USER);
1437
+ expect(versionControl.releaseLockUntouched, p).toHaveBeenCalledWith('ws-feat', 'feat', p, USER);
1479
1438
  }
1480
- expect(workflow.releaseLockNoCommit).not.toHaveBeenCalled();
1481
- expect(workflow.releaseLock).not.toHaveBeenCalled();
1439
+ expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
1440
+ expect(versionControl.releaseLock).not.toHaveBeenCalled();
1482
1441
  expect(await fs.readFile(onDisk(TAKEN), 'utf-8')).toBe('the winner of the race');
1483
1442
  });
1484
1443
 
@@ -1489,10 +1448,10 @@ describe('LockingFilesystem — a destination that is taken is refused, never re
1489
1448
  * than discarding the winner's queued file.
1490
1449
  */
1491
1450
  it('an exclusive write onto a taken name is the same refusal, and releases UNTOUCHED', async () => {
1492
- const workflow = makeWorkflow();
1451
+ const versionControl = makeVersionControl();
1493
1452
  const layer = new LockingFilesystem(
1494
1453
  { basePath: root, contained: true },
1495
- { workflow, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
1454
+ { versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
1496
1455
  );
1497
1456
  await fs.writeFile(onDisk(TAKEN), 'the winner of the race');
1498
1457
 
@@ -1501,9 +1460,9 @@ describe('LockingFilesystem — a destination that is taken is refused, never re
1501
1460
  message: 'A file named taken.md already exists in KnowledgeBase.',
1502
1461
  });
1503
1462
 
1504
- expect(workflow.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', TAKEN, USER);
1505
- expect(workflow.releaseLockNoCommit).not.toHaveBeenCalled();
1506
- expect(workflow.releaseLock).not.toHaveBeenCalled();
1463
+ expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', TAKEN, USER);
1464
+ expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
1465
+ expect(versionControl.releaseLock).not.toHaveBeenCalled();
1507
1466
  expect(await fs.readFile(onDisk(TAKEN), 'utf-8')).toBe('the winner of the race');
1508
1467
  });
1509
1468