@bevel-software/platform-core-backend 0.22.0 → 0.23.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 (325) hide show
  1. package/dist/core/core-ports.d.ts +11 -1
  2. package/dist/core/core-ports.d.ts.map +1 -1
  3. package/dist/core/core-ports.js.map +1 -1
  4. package/dist/core/create-core-server.d.ts +3 -3
  5. package/dist/core/create-core-server.d.ts.map +1 -1
  6. package/dist/core/create-core-server.js +30 -11
  7. package/dist/core/create-core-server.js.map +1 -1
  8. package/dist/core/create-core-services.d.ts +9 -2
  9. package/dist/core/create-core-services.d.ts.map +1 -1
  10. package/dist/core/create-core-services.js +112 -23
  11. package/dist/core/create-core-services.js.map +1 -1
  12. package/dist/core/lifecycle.d.ts +34 -1
  13. package/dist/core/lifecycle.d.ts.map +1 -1
  14. package/dist/core/lifecycle.js +89 -13
  15. package/dist/core/lifecycle.js.map +1 -1
  16. package/dist/core-config.d.ts +0 -12
  17. package/dist/core-config.d.ts.map +1 -1
  18. package/dist/core-config.js +11 -13
  19. package/dist/core-config.js.map +1 -1
  20. package/dist/index.d.ts +3 -2
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +2 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/modules/access/access-control.interface.d.ts +42 -12
  25. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  26. package/dist/modules/access/access-control.service.d.ts +67 -10
  27. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  28. package/dist/modules/access/access-control.service.js +214 -35
  29. package/dist/modules/access/access-control.service.js.map +1 -1
  30. package/dist/modules/access/access.routes.d.ts.map +1 -1
  31. package/dist/modules/access/access.routes.js +13 -15
  32. package/dist/modules/access/access.routes.js.map +1 -1
  33. package/dist/modules/auth/account-admission.d.ts +58 -7
  34. package/dist/modules/auth/account-admission.d.ts.map +1 -1
  35. package/dist/modules/auth/account-admission.js +44 -1
  36. package/dist/modules/auth/account-admission.js.map +1 -1
  37. package/dist/modules/auth/account.routes.d.ts +1 -1
  38. package/dist/modules/auth/account.routes.d.ts.map +1 -1
  39. package/dist/modules/auth/account.routes.js +61 -5
  40. package/dist/modules/auth/account.routes.js.map +1 -1
  41. package/dist/modules/auth/auth.middleware.d.ts +1 -1
  42. package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
  43. package/dist/modules/auth/auth.middleware.js +18 -7
  44. package/dist/modules/auth/auth.middleware.js.map +1 -1
  45. package/dist/modules/auth/auth.routes.d.ts.map +1 -1
  46. package/dist/modules/auth/auth.routes.js +8 -0
  47. package/dist/modules/auth/auth.routes.js.map +1 -1
  48. package/dist/modules/auth/auth.service.d.ts +70 -2
  49. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  50. package/dist/modules/auth/auth.service.js +176 -7
  51. package/dist/modules/auth/auth.service.js.map +1 -1
  52. package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
  53. package/dist/modules/auth/oidc-auth-provider.js +7 -2
  54. package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
  55. package/dist/modules/database/core-schema.d.ts +41 -24
  56. package/dist/modules/database/core-schema.d.ts.map +1 -1
  57. package/dist/modules/database/core-schema.js +35 -27
  58. package/dist/modules/database/core-schema.js.map +1 -1
  59. package/dist/modules/kb-fs/branch-name.d.ts.map +1 -1
  60. package/dist/modules/kb-fs/branch-name.js +12 -2
  61. package/dist/modules/kb-fs/branch-name.js.map +1 -1
  62. package/dist/modules/kb-fs/repo-path.d.ts +11 -16
  63. package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
  64. package/dist/modules/kb-fs/repo-path.js +79 -0
  65. package/dist/modules/kb-fs/repo-path.js.map +1 -1
  66. package/dist/modules/kb-sync/kb-sync.routes.d.ts +2 -2
  67. package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -1
  68. package/dist/modules/kb-sync/kb-sync.routes.js +14 -3
  69. package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -1
  70. package/dist/modules/kb-sync/sync-auth.d.ts +3 -1
  71. package/dist/modules/kb-sync/sync-auth.d.ts.map +1 -1
  72. package/dist/modules/kb-sync/sync-auth.js +1 -1
  73. package/dist/modules/kb-sync/sync-auth.js.map +1 -1
  74. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  75. package/dist/modules/mcp/mcp-auth.middleware.js +21 -6
  76. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  77. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  78. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +3 -2
  79. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  80. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
  81. package/dist/modules/settings/deployment-settings.service.js +8 -3
  82. package/dist/modules/settings/deployment-settings.service.js.map +1 -1
  83. package/dist/modules/settings/setup.routes.d.ts +52 -1
  84. package/dist/modules/settings/setup.routes.d.ts.map +1 -1
  85. package/dist/modules/settings/setup.routes.js +207 -19
  86. package/dist/modules/settings/setup.routes.js.map +1 -1
  87. package/dist/modules/skills/skills.contract.d.ts +90 -18
  88. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  89. package/dist/modules/skills/skills.contract.js +4 -1
  90. package/dist/modules/skills/skills.contract.js.map +1 -1
  91. package/dist/modules/skills/skills.service.d.ts +98 -9
  92. package/dist/modules/skills/skills.service.d.ts.map +1 -1
  93. package/dist/modules/skills/skills.service.js +239 -37
  94. package/dist/modules/skills/skills.service.js.map +1 -1
  95. package/dist/modules/skills/skills.tools.d.ts +7 -0
  96. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  97. package/dist/modules/skills/skills.tools.js +71 -7
  98. package/dist/modules/skills/skills.tools.js.map +1 -1
  99. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  100. package/dist/modules/tool-auth/external-api-key.service.js +4 -2
  101. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  102. package/dist/modules/tool-auth/internal-token.service.d.ts +3 -3
  103. package/dist/modules/tool-auth/tool-auth.middleware.d.ts +16 -7
  104. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  105. package/dist/modules/tool-auth/tool-auth.middleware.js +34 -12
  106. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  107. package/dist/modules/tool-helpers/tool-handler.d.ts +2 -1
  108. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  109. package/dist/modules/tool-helpers/tool-handler.js +25 -4
  110. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  111. package/dist/modules/tool-helpers/tool.contract.d.ts +3 -2
  112. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  113. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  114. package/dist/modules/tool-helpers/validate-token.js +1 -1
  115. package/dist/modules/tool-helpers/validate-token.js.map +1 -1
  116. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts +98 -0
  117. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +1 -0
  118. package/dist/modules/workflow/agent-tools/change-request-summary.js +81 -0
  119. package/dist/modules/workflow/agent-tools/change-request-summary.js.map +1 -0
  120. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  121. package/dist/modules/workflow/agent-tools/workflow.tools.js +75 -35
  122. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  123. package/dist/modules/workflow/file-lock.service.d.ts +24 -0
  124. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  125. package/dist/modules/workflow/file-lock.service.js +30 -0
  126. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  127. package/dist/modules/workflow/git/git.service.d.ts +69 -0
  128. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  129. package/dist/modules/workflow/git/git.service.js +153 -2
  130. package/dist/modules/workflow/git/git.service.js.map +1 -1
  131. package/dist/modules/workflow/pending-commits.service.d.ts +38 -0
  132. package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
  133. package/dist/modules/workflow/pending-commits.service.js +55 -0
  134. package/dist/modules/workflow/pending-commits.service.js.map +1 -1
  135. package/dist/modules/workflow/workflow-hooks.d.ts +54 -32
  136. package/dist/modules/workflow/workflow-hooks.d.ts.map +1 -1
  137. package/dist/modules/workflow/workflow-hooks.js +16 -1
  138. package/dist/modules/workflow/workflow-hooks.js.map +1 -1
  139. package/dist/modules/workflow/workflow.service.d.ts +60 -6
  140. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  141. package/dist/modules/workflow/workflow.service.js +111 -4
  142. package/dist/modules/workflow/workflow.service.js.map +1 -1
  143. package/dist/modules/workspace/agent-access.gate.d.ts +94 -0
  144. package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -0
  145. package/dist/modules/workspace/agent-access.gate.js +123 -0
  146. package/dist/modules/workspace/agent-access.gate.js.map +1 -0
  147. package/dist/modules/workspace/routine-write-policy.d.ts +5 -6
  148. package/dist/modules/workspace/routine-write-policy.d.ts.map +1 -1
  149. package/dist/modules/workspace/routine-write-policy.js +5 -6
  150. package/dist/modules/workspace/routine-write-policy.js.map +1 -1
  151. package/dist/modules/workspace/session-sink.d.ts +5 -5
  152. package/dist/modules/workspace/set-aside-clone.d.ts +46 -0
  153. package/dist/modules/workspace/set-aside-clone.d.ts.map +1 -0
  154. package/dist/modules/workspace/set-aside-clone.js +92 -0
  155. package/dist/modules/workspace/set-aside-clone.js.map +1 -0
  156. package/dist/modules/workspace/startup/kb-startup-runner.d.ts +59 -9
  157. package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
  158. package/dist/modules/workspace/startup/kb-startup-runner.js +65 -24
  159. package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
  160. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  161. package/dist/modules/workspace/workspace.routes.js +92 -3
  162. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  163. package/dist/modules/workspace/workspace.service.d.ts +115 -5
  164. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  165. package/dist/modules/workspace/workspace.service.js +255 -25
  166. package/dist/modules/workspace/workspace.service.js.map +1 -1
  167. package/dist/modules/workspace/workspace.tools.d.ts +2 -2
  168. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  169. package/dist/modules/workspace/workspace.tools.js +309 -115
  170. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  171. package/dist/modules/write-access/write-access.d.ts +60 -0
  172. package/dist/modules/write-access/write-access.d.ts.map +1 -0
  173. package/dist/modules/write-access/write-access.js +129 -0
  174. package/dist/modules/write-access/write-access.js.map +1 -0
  175. package/dist/shared/domain-errors.d.ts +25 -0
  176. package/dist/shared/domain-errors.d.ts.map +1 -1
  177. package/dist/shared/domain-errors.js +28 -0
  178. package/dist/shared/domain-errors.js.map +1 -1
  179. package/dist/shared/git.contract.d.ts +20 -0
  180. package/dist/shared/git.contract.d.ts.map +1 -1
  181. package/dist/shared/git.contract.js +26 -0
  182. package/dist/shared/git.contract.js.map +1 -1
  183. package/dist/tenancy/static-tenant-source.d.ts +0 -1
  184. package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
  185. package/dist/tenancy/static-tenant-source.js +0 -2
  186. package/dist/tenancy/static-tenant-source.js.map +1 -1
  187. package/migrations/0014_change_request_closed_reason.sql +1 -0
  188. package/migrations/0015_account_deactivation.sql +3 -0
  189. package/migrations/meta/0014_snapshot.json +2265 -0
  190. package/migrations/meta/0015_snapshot.json +2271 -0
  191. package/migrations/meta/_journal.json +14 -0
  192. package/package.json +3 -3
  193. package/src/__tests__/kb-layout-config.test.ts +0 -2
  194. package/src/__tests__/retired-settings.test.ts +96 -0
  195. package/src/core/__tests__/gated-boot.test.ts +132 -0
  196. package/src/core/__tests__/lifecycle.test.ts +170 -1
  197. package/src/core/__tests__/set-aside-root-is-one-place.test.ts +63 -0
  198. package/src/core/core-ports.ts +11 -1
  199. package/src/core/create-core-server.ts +32 -14
  200. package/src/core/create-core-services.ts +129 -23
  201. package/src/core/lifecycle.ts +98 -13
  202. package/src/core-config.ts +13 -14
  203. package/src/index.ts +12 -1
  204. package/src/modules/access/__tests__/access-control.preview-relocation.test.ts +385 -0
  205. package/src/modules/access/__tests__/access-control.prospective.test.ts +94 -18
  206. package/src/modules/access/__tests__/access.routes.prospective.test.ts +6 -7
  207. package/src/modules/access/access-control.interface.ts +43 -12
  208. package/src/modules/access/access-control.service.ts +234 -36
  209. package/src/modules/access/access.routes.ts +13 -15
  210. package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +3 -1
  211. package/src/modules/auth/__tests__/account-deactivation.test.ts +252 -0
  212. package/src/modules/auth/__tests__/account.routes.test.ts +81 -3
  213. package/src/modules/auth/__tests__/auth.middleware.test.ts +45 -22
  214. package/src/modules/auth/__tests__/auth.service.test.ts +1 -1
  215. package/src/modules/auth/account-admission.ts +78 -9
  216. package/src/modules/auth/account.routes.ts +63 -7
  217. package/src/modules/auth/auth.middleware.ts +19 -8
  218. package/src/modules/auth/auth.routes.ts +8 -0
  219. package/src/modules/auth/auth.service.ts +179 -6
  220. package/src/modules/auth/oidc-auth-provider.ts +7 -2
  221. package/src/modules/database/core-schema.ts +35 -27
  222. package/src/modules/kb-fs/__tests__/branch-name.test.ts +10 -0
  223. package/src/modules/kb-fs/__tests__/repo-path.test.ts +123 -0
  224. package/src/modules/kb-fs/branch-name.ts +14 -1
  225. package/src/modules/kb-fs/repo-path.ts +85 -0
  226. package/src/modules/kb-sync/__tests__/kb-sync.routes.test.ts +26 -1
  227. package/src/modules/kb-sync/kb-sync.routes.ts +14 -4
  228. package/src/modules/kb-sync/sync-auth.ts +2 -2
  229. package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +60 -3
  230. package/src/modules/mcp/__tests__/mcp.service.test.ts +68 -6
  231. package/src/modules/mcp/mcp-auth.middleware.ts +20 -6
  232. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +3 -1
  233. package/src/modules/plugins/__tests__/plugins.tools.test.ts +2 -1
  234. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +16 -0
  235. package/src/modules/settings/__tests__/setup.routes.git-mode.test.ts +91 -58
  236. package/src/modules/settings/__tests__/setup.routes.github-app.test.ts +25 -8
  237. package/src/modules/settings/__tests__/setup.routes.managed-phase.test.ts +14 -11
  238. package/src/modules/settings/__tests__/setup.routes.repository-change.test.ts +390 -0
  239. package/src/modules/settings/__tests__/setup.routes.test.ts +3 -0
  240. package/src/modules/settings/deployment-settings.service.ts +8 -3
  241. package/src/modules/settings/setup.routes.ts +263 -19
  242. package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +2 -1
  243. package/src/modules/skills/__tests__/branch-skills.tools.test.ts +218 -0
  244. package/src/modules/skills/__tests__/skills.service.test.ts +229 -5
  245. package/src/modules/skills/skills.contract.ts +91 -18
  246. package/src/modules/skills/skills.service.ts +278 -41
  247. package/src/modules/skills/skills.tools.ts +80 -8
  248. package/src/modules/tool-auth/__tests__/manual-auth.middleware.test.ts +14 -1
  249. package/src/modules/tool-auth/external-api-key.service.ts +4 -2
  250. package/src/modules/tool-auth/internal-token.service.ts +3 -3
  251. package/src/modules/tool-auth/tool-auth.middleware.ts +34 -11
  252. package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +5 -3
  253. package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +3 -3
  254. package/src/modules/tool-helpers/__tests__/validate-token.test.ts +11 -1
  255. package/src/modules/tool-helpers/tool-handler.ts +24 -4
  256. package/src/modules/tool-helpers/tool.contract.ts +3 -2
  257. package/src/modules/tool-helpers/validate-token.ts +1 -1
  258. package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +111 -0
  259. package/src/modules/workflow/__tests__/workflow.routes.history-read-gate.test.ts +25 -0
  260. package/src/modules/workflow/__tests__/workflow.service.repository-replaced.test.ts +249 -0
  261. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +264 -6
  262. package/src/modules/workflow/agent-tools/change-request-summary.ts +182 -0
  263. package/src/modules/workflow/agent-tools/workflow.tools.ts +86 -35
  264. package/src/modules/workflow/file-lock.service.ts +31 -0
  265. package/src/modules/workflow/git/__tests__/git.service.fileBytesAtCommit.test.ts +260 -0
  266. package/src/modules/workflow/git/git.service.ts +168 -0
  267. package/src/modules/workflow/pending-commits.service.ts +59 -1
  268. package/src/modules/workflow/workflow-hooks.ts +64 -26
  269. package/src/modules/workflow/workflow.service.ts +121 -4
  270. package/src/modules/workspace/__tests__/agent-access.gate.test.ts +208 -0
  271. package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +384 -0
  272. package/src/modules/workspace/__tests__/file-stat-access.test.ts +2 -1
  273. package/src/modules/workspace/__tests__/git-internals.security.test.ts +2 -1
  274. package/src/modules/workspace/__tests__/set-aside-clone.test.ts +52 -0
  275. package/src/modules/workspace/__tests__/workspace.routes.at-ref.test.ts +305 -0
  276. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +2 -0
  277. package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +147 -0
  278. package/src/modules/workspace/__tests__/workspace.service.replaced-repository.test.ts +326 -0
  279. package/src/modules/workspace/__tests__/workspace.service.test.ts +20 -9
  280. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +2 -3
  281. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +2 -3
  282. package/src/modules/workspace/__tests__/workspace.tools.test.ts +685 -14
  283. package/src/modules/workspace/agent-access.gate.ts +164 -0
  284. package/src/modules/workspace/routine-write-policy.ts +5 -6
  285. package/src/modules/workspace/session-sink.ts +5 -5
  286. package/src/modules/workspace/set-aside-clone.ts +96 -0
  287. package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +86 -0
  288. package/src/modules/workspace/startup/kb-startup-runner.ts +115 -34
  289. package/src/modules/workspace/workspace.routes.ts +95 -3
  290. package/src/modules/workspace/workspace.service.ts +256 -24
  291. package/src/modules/workspace/workspace.tools.ts +338 -119
  292. package/src/modules/write-access/__tests__/write-access.test.ts +248 -0
  293. package/src/modules/write-access/write-access.ts +153 -0
  294. package/src/shared/domain-errors.ts +31 -0
  295. package/src/shared/git.contract.ts +27 -0
  296. package/src/tenancy/__tests__/static-tenant-source.test.ts +0 -1
  297. package/src/tenancy/static-tenant-source.ts +0 -3
  298. package/dist/modules/workflow/session-ontology.policy.d.ts +0 -52
  299. package/dist/modules/workflow/session-ontology.policy.d.ts.map +0 -1
  300. package/dist/modules/workflow/session-ontology.policy.js +0 -62
  301. package/dist/modules/workflow/session-ontology.policy.js.map +0 -1
  302. package/dist/modules/workflow/session-ontology.service.d.ts +0 -105
  303. package/dist/modules/workflow/session-ontology.service.d.ts.map +0 -1
  304. package/dist/modules/workflow/session-ontology.service.js +0 -147
  305. package/dist/modules/workflow/session-ontology.service.js.map +0 -1
  306. package/dist/modules/workspace/session-ontology.gate.d.ts +0 -114
  307. package/dist/modules/workspace/session-ontology.gate.d.ts.map +0 -1
  308. package/dist/modules/workspace/session-ontology.gate.js +0 -161
  309. package/dist/modules/workspace/session-ontology.gate.js.map +0 -1
  310. package/dist/shared/kb-layout.d.ts +0 -39
  311. package/dist/shared/kb-layout.d.ts.map +0 -1
  312. package/dist/shared/kb-layout.js +0 -103
  313. package/dist/shared/kb-layout.js.map +0 -1
  314. package/dist/shared/kb-layout.test.d.ts +0 -2
  315. package/dist/shared/kb-layout.test.d.ts.map +0 -1
  316. package/dist/shared/kb-layout.test.js +0 -75
  317. package/dist/shared/kb-layout.test.js.map +0 -1
  318. package/src/modules/workflow/__tests__/session-ontology.policy.test.ts +0 -62
  319. package/src/modules/workflow/__tests__/session-ontology.service.test.ts +0 -201
  320. package/src/modules/workflow/session-ontology.policy.ts +0 -70
  321. package/src/modules/workflow/session-ontology.service.ts +0 -183
  322. package/src/modules/workspace/__tests__/session-ontology.gate.test.ts +0 -239
  323. package/src/modules/workspace/session-ontology.gate.ts +0 -191
  324. package/src/shared/kb-layout.test.ts +0 -98
  325. package/src/shared/kb-layout.ts +0 -102
@@ -1,103 +0,0 @@
1
- import { ONTOLOGY_MARKERS } from '@bevel-software/platform-shared';
2
- /**
3
- * Resolve which named ontology a KB path belongs to — the backend-side
4
- * implementation of the layout contract whose constants/types live in
5
- * `@bevel-software/platform-shared` (`kb-layout.ts`). It is backend-only and consumed by
6
- * more than one domain module (the `workspace` read gate and the `workflow`
7
- * session-ontology service), so it lives in `backend/src/shared` rather than in
8
- * any single module — importable by both modules without crossing a module's
9
- * internal boundary. Note it does NOT mirror the kb-graph parser one-to-one:
10
- * see the `ontologyOf` docstring for the deliberate `Data/` exemption.
11
- *
12
- * Pure string handling — no IO, no DB.
13
- */
14
- /**
15
- * Split a path into repo-root-relative POSIX segments, dropping `./`,
16
- * leading/trailing slashes, and an optional `kbDirName` prefix (the on-disk
17
- * clone folder, e.g. `knowledge-base/`). Pure string handling — no IO.
18
- */
19
- function toSegments(path, kbDirName) {
20
- // Collapse to canonical segments: backslashes → `/`, drop empty and `.`
21
- // segments, resolve `..` against the accumulated prefix. Canonicalizing the
22
- // inner segments (not just the ends) means equivalent paths like
23
- // `KnowledgeBase/Product/../Product/Knowledge/x.md` and
24
- // `KnowledgeBase/Product/Knowledge/x.md` resolve to the same ontology.
25
- const collapse = (s) => {
26
- const out = [];
27
- for (const seg of s.replace(/\\/g, '/').split('/')) {
28
- if (seg === '' || seg === '.')
29
- continue;
30
- if (seg === '..') {
31
- // Pop a real segment; a leading `..` (nothing to pop) is kept so it can
32
- // never alias into a sibling ontology.
33
- if (out.length > 0 && out[out.length - 1] !== '..')
34
- out.pop();
35
- else
36
- out.push('..');
37
- continue;
38
- }
39
- out.push(seg);
40
- }
41
- return out;
42
- };
43
- let segs = collapse(path);
44
- const prefixSegs = kbDirName ? collapse(kbDirName) : [];
45
- if (prefixSegs.length > 0 && prefixSegs.every((s, i) => segs[i] === s)) {
46
- segs = segs.slice(prefixSegs.length);
47
- }
48
- return segs;
49
- }
50
- /**
51
- * Resolve the named ontology a KB path belongs to, or `null` (neutral) when it
52
- * belongs to none.
53
- *
54
- * Classification is deliberately NARROWER than the kb-graph parser: the parser
55
- * discovers ontologies under every `ONTOLOGY_ROOTS` entry (`KnowledgeBase/`
56
- * AND `Data/`), but this function classifies ONLY `KnowledgeBase/` paths.
57
- * `Data/` ontologies are parsed into the graph yet are intentionally NOT
58
- * classified here, so the session-ontology gate treats `Data/` paths as
59
- * neutral — pipeline agents read knowledge and write data records in one
60
- * session (owner decision 2026-07-27; the cross-ontology boundary mechanism
61
- * itself may be redesigned). This divergence is on purpose, not drift.
62
- *
63
- * Within `KnowledgeBase/`, the shape matches the parser (`parser.ts`
64
- * `walkForOntologies`): an ontology is the directory that holds both a
65
- * `Knowledge/` and a `NodeTypes/` subfolder. That directory can sit at ANY
66
- * depth under `KnowledgeBase/`, so the ontology id is the path PREFIX up to
67
- * (but excluding) the first `Knowledge/` or `NodeTypes/` marker segment — as
68
- * long as that prefix is inside `KnowledgeBase/` and is not `KnowledgeBase/`
69
- * itself.
70
- *
71
- * KnowledgeBase/Product/Knowledge/Foo.md → "KnowledgeBase/Product"
72
- * KnowledgeBase/IT Architecture/NodeTypes/T.md → "KnowledgeBase/IT Architecture"
73
- * KnowledgeBase/Group/Sub/Knowledge/x.md → "KnowledgeBase/Group/Sub"
74
- * KnowledgeBase/Knowledge/Foo.md → null (marker directly under KB root)
75
- * Knowledge/Foo.md, Skills/x.md, access.md → null (not under KnowledgeBase/)
76
- * Data/Ops/Knowledge/x.md → null (Data/ is exempt — see above)
77
- * KnowledgeBase/Product/Uploads/x.png → null (no marker segment)
78
- *
79
- * Uses exact segment equality (never substring), so `KnowledgeBase/` and
80
- * `Knowledge-notes.md` are never treated as markers; space-safe; pure (no IO).
81
- *
82
- * @param path repo-root-relative POSIX path (may carry a `kbDirName` prefix)
83
- * @param layout the knowledge base's layout — names its `KnowledgeBase/` root
84
- * @param kbDirName optional on-disk clone folder prefix to strip first
85
- */
86
- export function ontologyOf(path, layout, kbDirName) {
87
- const segs = toSegments(path, kbDirName);
88
- // Must live under the dedicated KnowledgeBase/ container, with at least one
89
- // ontology-name segment and a marker after it (so length >= 3).
90
- if (segs.length < 3 || segs[0] !== layout.knowledgeBaseDir)
91
- return null;
92
- // The ontology id is the prefix before the first marker segment. Require at
93
- // least one name segment between `KnowledgeBase/` and the marker, so a marker
94
- // sitting directly under `KnowledgeBase/` (`KnowledgeBase/Knowledge/...`) is
95
- // neutral, matching the parser (it only discovers ontologies one level down).
96
- for (let i = 1; i < segs.length; i++) {
97
- if (ONTOLOGY_MARKERS.has(segs[i])) {
98
- return i >= 2 ? segs.slice(0, i).join('/') : null;
99
- }
100
- }
101
- return null;
102
- }
103
- //# sourceMappingURL=kb-layout.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"kb-layout.js","sourceRoot":"","sources":["../../src/shared/kb-layout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAgC,MAAM,iCAAiC,CAAC;AAEjG;;;;;;;;;;;GAWG;AAEH;;;;GAIG;AACH,SAAS,UAAU,CAAC,IAAY,EAAE,SAAkB;IAClD,wEAAwE;IACxE,4EAA4E;IAC5E,iEAAiE;IACjE,wDAAwD;IACxD,uEAAuE;IACvE,MAAM,QAAQ,GAAG,CAAC,CAAS,EAAY,EAAE;QACvC,MAAM,GAAG,GAAa,EAAE,CAAC;QACzB,KAAK,MAAM,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YACnD,IAAI,GAAG,KAAK,EAAE,IAAI,GAAG,KAAK,GAAG;gBAAE,SAAS;YACxC,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;gBACjB,wEAAwE;gBACxE,uCAAuC;gBACvC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,IAAI;oBAAE,GAAG,CAAC,GAAG,EAAE,CAAC;;oBACzD,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACpB,SAAS;YACX,CAAC;YACD,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;IAEF,IAAI,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC1B,MAAM,UAAU,GAAG,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACxD,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;QACvE,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,MAAgB,EAAE,SAAkB;IAC3E,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IACzC,4EAA4E;IAC5E,gEAAgE;IAChE,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,gBAAgB;QAAE,OAAO,IAAI,CAAC;IACxE,4EAA4E;IAC5E,8EAA8E;IAC9E,6EAA6E;IAC7E,8EAA8E;IAC9E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,IAAI,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAClC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACpD,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=kb-layout.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"kb-layout.test.d.ts","sourceRoot":"","sources":["../../src/shared/kb-layout.test.ts"],"names":[],"mappings":""}
@@ -1,75 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { DEFAULT_KB_LAYOUT } from '@bevel-software/platform-shared';
3
- import { ontologyOf } from './kb-layout.js';
4
- /** `ontologyOf` under the default layout, which is what these cases are written against. */
5
- const ont = (path, kbDirName) => ontologyOf(path, DEFAULT_KB_LAYOUT, kbDirName);
6
- describe('ontologyOf', () => {
7
- it('classifies under the knowledge-base root the layout names, not the default one', () => {
8
- const layout = { ...DEFAULT_KB_LAYOUT, knowledgeBaseDir: 'docs' };
9
- expect(ontologyOf('docs/Product/Knowledge/Foo.md', layout)).toBe('docs/Product');
10
- expect(ontologyOf('KnowledgeBase/Product/Knowledge/Foo.md', layout)).toBeNull();
11
- });
12
- it('resolves a named ontology under Knowledge', () => {
13
- expect(ont('KnowledgeBase/Product/Knowledge/Foo.md')).toBe('KnowledgeBase/Product');
14
- });
15
- it('resolves a named ontology under NodeTypes', () => {
16
- expect(ont('KnowledgeBase/Product/NodeTypes/Process.md')).toBe('KnowledgeBase/Product');
17
- });
18
- it('is space-safe in the ontology name', () => {
19
- expect(ont('KnowledgeBase/IT Architecture/NodeTypes/ServiceCommitment.md')).toBe('KnowledgeBase/IT Architecture');
20
- });
21
- it('treats a nested ontology directory as the full prefix before the marker', () => {
22
- expect(ont('KnowledgeBase/Plugin/Sub/Knowledge/x.md')).toBe('KnowledgeBase/Plugin/Sub');
23
- });
24
- it('returns null when the marker sits directly under KnowledgeBase/', () => {
25
- expect(ont('KnowledgeBase/Knowledge/Foo.md')).toBeNull();
26
- expect(ont('KnowledgeBase/NodeTypes/T.md')).toBeNull();
27
- });
28
- it('returns null for root-level Knowledge (not under KnowledgeBase/)', () => {
29
- expect(ont('Knowledge/Foo.md')).toBeNull();
30
- });
31
- it('returns null for Data/ ontologies (deliberate session-ontology exemption)', () => {
32
- // `Data/<X>` IS parsed into the graph (parser `ONTOLOGY_ROOTS`), but is
33
- // intentionally NOT classified here so the session-ontology gate treats
34
- // Data paths as neutral — pipeline agents read knowledge and write data
35
- // in one session (owner decision 2026-07-27). See the `ontologyOf`
36
- // docstring; this is not drift.
37
- expect(ont('Data/Ops/Knowledge/x.md')).toBeNull();
38
- expect(ont('Data/Ops/NodeTypes/Ticket.md')).toBeNull();
39
- // KnowledgeBase paths still classify as before.
40
- expect(ont('KnowledgeBase/Ops/Knowledge/x.md')).toBe('KnowledgeBase/Ops');
41
- });
42
- it('returns null for Plugins and root config (neutral)', () => {
43
- expect(ont('Plugins/some-skill/SKILL.md')).toBeNull();
44
- expect(ont('access.md')).toBeNull();
45
- expect(ont('roles.yaml')).toBeNull();
46
- });
47
- it('returns null for an ontology path with no marker segment', () => {
48
- expect(ont('KnowledgeBase/Product/Uploads/diagram.png')).toBeNull();
49
- });
50
- it('does not match substring lookalikes (segment equality)', () => {
51
- expect(ont('KnowledgeBase/Product/Knowledge-notes.md')).toBeNull();
52
- expect(ont('KnowledgeBase/Product/KnowledgeBase/x.md')).toBeNull();
53
- });
54
- it('strips a kbDirName prefix (normalized and un-normalized)', () => {
55
- expect(ont('knowledge-base/KnowledgeBase/Product/Knowledge/Foo.md', 'knowledge-base')).toBe('KnowledgeBase/Product');
56
- expect(ont('./knowledge-base/KnowledgeBase/GTM/NodeTypes/T.md', 'knowledge-base/')).toBe('KnowledgeBase/GTM');
57
- });
58
- it('normalizes backslashes and leading/trailing slashes', () => {
59
- expect(ont('/KnowledgeBase/Product/Knowledge/Foo.md/')).toBe('KnowledgeBase/Product');
60
- expect(ont('KnowledgeBase\\Product\\Knowledge\\Foo.md')).toBe('KnowledgeBase/Product');
61
- });
62
- it('canonicalizes inner . and .. segments to the same ontology', () => {
63
- expect(ont('KnowledgeBase/Product/./Knowledge/Foo.md')).toBe('KnowledgeBase/Product');
64
- expect(ont('KnowledgeBase/Product/Sub/../Knowledge/Foo.md')).toBe('KnowledgeBase/Product');
65
- expect(ont('KnowledgeBase/Platform/../Product/Knowledge/Foo.md')).toBe('KnowledgeBase/Product');
66
- // `..` also collapses inside the stripped kbDirName prefix.
67
- expect(ont('knowledge-base/x/../KnowledgeBase/GTM/NodeTypes/T.md', 'knowledge-base')).toBe('KnowledgeBase/GTM');
68
- });
69
- it('returns null for empty or root paths', () => {
70
- expect(ont('')).toBeNull();
71
- expect(ont('KnowledgeBase')).toBeNull();
72
- expect(ont('KnowledgeBase/Product')).toBeNull();
73
- });
74
- });
75
- //# sourceMappingURL=kb-layout.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"kb-layout.test.js","sourceRoot":"","sources":["../../src/shared/kb-layout.test.ts"],"names":[],"mappings":"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"}
@@ -1,62 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { recordTouch, decideWrite } from '../session-ontology.policy.js';
3
-
4
- const PRODUCT = 'KnowledgeBase/Product';
5
- const PLATFORM = 'KnowledgeBase/Platform';
6
- const GTM = 'KnowledgeBase/GTM';
7
-
8
- describe('recordTouch', () => {
9
- it('adds a named ontology to the set', () => {
10
- expect([...recordTouch(new Set(), PRODUCT)]).toEqual([PRODUCT]);
11
- });
12
-
13
- it('adds nothing for a neutral touch', () => {
14
- expect([...recordTouch(new Set([PRODUCT]), null)]).toEqual([PRODUCT]);
15
- });
16
-
17
- it('accumulates across ontologies', () => {
18
- const a = recordTouch(new Set(), PRODUCT);
19
- const b = recordTouch(a, PLATFORM);
20
- expect(new Set(b)).toEqual(new Set([PRODUCT, PLATFORM]));
21
- });
22
-
23
- it('does not mutate the input set', () => {
24
- const input = new Set([PRODUCT]);
25
- recordTouch(input, PLATFORM);
26
- expect([...input]).toEqual([PRODUCT]);
27
- });
28
- });
29
-
30
- describe('decideWrite', () => {
31
- it('allows a write to a neutral path regardless of touched set', () => {
32
- expect(decideWrite(new Set([PRODUCT, PLATFORM]), null)).toEqual({ allow: true });
33
- });
34
-
35
- it('allows a write when the session has touched nothing yet', () => {
36
- expect(decideWrite(new Set(), PRODUCT)).toEqual({ allow: true });
37
- });
38
-
39
- it('allows a write to the single ontology the session is confined to', () => {
40
- expect(decideWrite(new Set([PRODUCT]), PRODUCT)).toEqual({ allow: true });
41
- });
42
-
43
- it('blocks a write to a different ontology when confined to one (different-ontology)', () => {
44
- expect(decideWrite(new Set([PRODUCT]), PLATFORM)).toEqual({
45
- allow: false,
46
- reason: 'different-ontology',
47
- touched: [PRODUCT],
48
- attempted: PLATFORM,
49
- });
50
- });
51
-
52
- it('blocks ALL writes once the session has touched two ontologies (multi-ontology)', () => {
53
- // Reading across ontologies poisons writes — even a write back into Product.
54
- expect(decideWrite(new Set([PRODUCT, PLATFORM]), PRODUCT)).toEqual({
55
- allow: false,
56
- reason: 'multi-ontology',
57
- touched: [PLATFORM, PRODUCT],
58
- attempted: PRODUCT,
59
- });
60
- expect(decideWrite(new Set([PRODUCT, PLATFORM]), GTM).allow).toBe(false);
61
- });
62
- });
@@ -1,201 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- import { testKbContext } from '../../../__tests__/kb-context.js';
3
-
4
- import { SessionOntologyService } from '../session-ontology.service.js';
5
- import type { Database } from '../../database/connection.js';
6
-
7
- const PRODUCT = 'KnowledgeBase/Product';
8
- const PLATFORM = 'KnowledgeBase/Platform';
9
- const P_READ = 'KnowledgeBase/Product/Knowledge/Foo.md';
10
- const PL_READ = 'KnowledgeBase/Platform/Knowledge/Bar.md';
11
-
12
- /**
13
- * Faithful in-memory fake of the slice of the Drizzle `Database` the service
14
- * uses against the `session_ontology_touches` table (one row per
15
- * `(session_id, ontology)`):
16
- * - `insert().onConflictDoNothing()` adds a row unless the PK pair exists.
17
- * - `select({ontology}).from().where(eq(session))` returns the session's rows.
18
- * - `delete().where(eq(session) | lt(touchedAt, cutoff))` removes rows.
19
- *
20
- * A live-Postgres integration test (real PK conflict, real `touched_at < now()`
21
- * sweep) is still owed once the repo grows a Postgres test harness (tasks 3.4).
22
- */
23
- interface Row {
24
- sessionId: string;
25
- ontology: string;
26
- touchedAt: Date;
27
- }
28
-
29
- function makeFakeDb() {
30
- const rows: Row[] = [];
31
- const db = {
32
- _rows: rows,
33
- insert() {
34
- return {
35
- values(v: { sessionId: string; ontology: string }) {
36
- return {
37
- onConflictDoNothing() {
38
- const exists = rows.some((r) => r.sessionId === v.sessionId && r.ontology === v.ontology);
39
- if (!exists) rows.push({ sessionId: v.sessionId, ontology: v.ontology, touchedAt: new Date() });
40
- return Promise.resolve([]);
41
- },
42
- };
43
- },
44
- };
45
- },
46
- select() {
47
- return {
48
- from() {
49
- return {
50
- async where(pred: { sessionId: string }) {
51
- return rows
52
- .filter((r) => r.sessionId === pred.sessionId)
53
- .map((r) => ({ ontology: r.ontology }));
54
- },
55
- };
56
- },
57
- };
58
- },
59
- delete() {
60
- return {
61
- where(pred: { sessionId?: string; cutoff?: Date }) {
62
- if (pred.sessionId !== undefined) {
63
- for (let i = rows.length - 1; i >= 0; i--) {
64
- if (rows[i].sessionId === pred.sessionId) rows.splice(i, 1);
65
- }
66
- return { async returning() { return []; } } as never;
67
- }
68
- const removed: { sessionId: string }[] = [];
69
- for (let i = rows.length - 1; i >= 0; i--) {
70
- if (pred.cutoff && rows[i].touchedAt < pred.cutoff) {
71
- removed.push({ sessionId: rows[i].sessionId });
72
- rows.splice(i, 1);
73
- }
74
- }
75
- return { async returning() { return removed; } } as never;
76
- },
77
- };
78
- },
79
- };
80
- return db;
81
- }
82
-
83
- vi.mock('drizzle-orm', async (importOriginal) => {
84
- const actual = await importOriginal<typeof import('drizzle-orm')>();
85
- return {
86
- ...actual,
87
- eq: (_col: unknown, val: string) => ({ sessionId: val }),
88
- lt: (_col: unknown, val: Date) => ({ cutoff: val }),
89
- };
90
- });
91
-
92
- function makeService() {
93
- const db = makeFakeDb();
94
- return { svc: new SessionOntologyService(db as unknown as Database, testKbContext()), db };
95
- }
96
-
97
- describe('SessionOntologyService (touched-set model)', () => {
98
- let svc: SessionOntologyService;
99
- let db: ReturnType<typeof makeFakeDb>;
100
-
101
- beforeEach(() => {
102
- ({ svc, db } = makeService());
103
- });
104
-
105
- it('records a read and allows it', async () => {
106
- const d = await svc.checkOperation('s1', P_READ, false);
107
- expect(d.allow).toBe(true);
108
- expect(await svc.getTouched('s1')).toEqual([PRODUCT]);
109
- });
110
-
111
- it('allows reads ACROSS ontologies (reads are never blocked)', async () => {
112
- expect((await svc.checkOperation('s1', P_READ, false)).allow).toBe(true);
113
- expect((await svc.checkOperation('s1', PL_READ, false)).allow).toBe(true);
114
- expect(await svc.getTouched('s1')).toEqual([PLATFORM, PRODUCT]);
115
- });
116
-
117
- it('allows a write while the session is confined to that one ontology', async () => {
118
- await svc.checkOperation('s1', P_READ, false);
119
- expect((await svc.checkOperation('s1', P_READ, true)).allow).toBe(true);
120
- });
121
-
122
- it('allows the first write with no prior reads and pins to it', async () => {
123
- expect((await svc.checkOperation('s1', P_READ, true)).allow).toBe(true);
124
- expect(await svc.getTouched('s1')).toEqual([PRODUCT]);
125
- });
126
-
127
- it('blocks a write to a different ontology than the one read (different-ontology)', async () => {
128
- // Load-bearing for the start_session↔ask unification: the touched-set is
129
- // keyed purely by sessionId, so a READ and a later WRITE that share ONE id
130
- // hit the same set and cross-ontology writes are refused. This only protects
131
- // the read→ask flow because both now run under the SAME id (start_session
132
- // mints a real chat thread; ask accepts it). If a refactor re-splits those
133
- // namespaces, the read and the ask write land in different touched-sets and
134
- // this guarantee silently evaporates — see workspace.tools.ts start_session.
135
- await svc.checkOperation('s1', P_READ, false);
136
- const d = await svc.checkOperation('s1', PL_READ, true);
137
- expect(d).toMatchObject({ allow: false, reason: 'different-ontology', attempted: PLATFORM });
138
- // The blocked write does NOT record Platform.
139
- expect(await svc.getTouched('s1')).toEqual([PRODUCT]);
140
- });
141
-
142
- it('blocks ALL writes once the session has read across two ontologies (multi-ontology)', async () => {
143
- await svc.checkOperation('s1', P_READ, false);
144
- await svc.checkOperation('s1', PL_READ, false); // reads across — poisons writes
145
- // Even a write back into Product is now blocked.
146
- const back = await svc.checkOperation('s1', P_READ, true);
147
- expect(back).toMatchObject({ allow: false, reason: 'multi-ontology' });
148
- const other = await svc.checkOperation('s1', PL_READ, true);
149
- expect(other.allow).toBe(false);
150
- });
151
-
152
- it('treats a neutral path as always-allowed and never recorded', async () => {
153
- const d = await svc.checkOperation('s1', 'access.md', true);
154
- expect(d.allow).toBe(true);
155
- expect(await svc.getTouched('s1')).toEqual([]);
156
- });
157
-
158
- it('survives a "restart": a new instance re-reads the touched set from the store', async () => {
159
- await svc.checkOperation('s1', P_READ, false);
160
- await svc.checkOperation('s1', PL_READ, false);
161
- const afterRestart = new SessionOntologyService(db as unknown as Database, testKbContext());
162
- // The poison persists across the restart: writes still blocked.
163
- expect((await afterRestart.checkOperation('s1', P_READ, true)).allow).toBe(false);
164
- });
165
-
166
- it('reclaims a session on delete so it can write again', async () => {
167
- await svc.checkOperation('s1', P_READ, false);
168
- await svc.checkOperation('s1', PL_READ, false);
169
- await svc.delete('s1');
170
- expect(await svc.getTouched('s1')).toEqual([]);
171
- expect((await svc.checkOperation('s1', P_READ, true)).allow).toBe(true);
172
- });
173
-
174
- it('sweeps abandoned sessions older than the cutoff', async () => {
175
- await svc.checkOperation('old', P_READ, false);
176
- db._rows.forEach((r) => { if (r.sessionId === 'old') r.touchedAt = new Date(Date.now() - 60_000); });
177
- const removed = await svc.sweepOlderThan(new Date(Date.now() - 30_000));
178
- expect(removed).toBe(1);
179
- expect(await svc.getTouched('old')).toEqual([]);
180
- });
181
-
182
- it('records each ontology only once (idempotent touches)', async () => {
183
- await svc.checkOperation('s1', P_READ, false);
184
- await svc.checkOperation('s1', P_READ, false);
185
- await svc.checkOperation('s1', P_READ, false);
186
- expect(db._rows.filter((r) => r.sessionId === 's1').length).toBe(1);
187
- });
188
-
189
- it('serializes two concurrent same-session writes to different ontologies (only one wins)', async () => {
190
- // Without per-session serialization both could observe an empty touched set
191
- // and both pin to a different ontology before either INSERT lands.
192
- const [a, b] = await Promise.all([
193
- svc.checkOperation('s1', P_READ, true), // write → Product
194
- svc.checkOperation('s1', PL_READ, true), // write → Platform (concurrent)
195
- ]);
196
- // Exactly one write is allowed; the loser is blocked as different-ontology.
197
- expect([a, b].filter((d) => d.allow)).toHaveLength(1);
198
- // Only the winner's ontology is recorded.
199
- expect(await svc.getTouched('s1')).toHaveLength(1);
200
- });
201
- });
@@ -1,70 +0,0 @@
1
- /**
2
- * Ontology-session boundary — the PURE decision core (no IO, no DB).
3
- *
4
- * Model: each run/session accumulates the SET of named ontologies it has
5
- * touched (via any read or write). The rule is asymmetric:
6
- *
7
- * READS — always allowed. A read that touches a named ontology RECORDS it
8
- * into the session's touched set (so it can poison later writes).
9
- * WRITES — allowed only while the session is confined to a SINGLE named
10
- * ontology and the write targets that same ontology. The moment a
11
- * session has touched two or more named ontologies (typically by
12
- * reading across them), EVERY subsequent write is blocked — "read
13
- * across ontologies → you can't write anymore at all."
14
- *
15
- * Neutral touches (`ontologyOf` → null: root config, Skills, root-level
16
- * Knowledge, non-KB) never record and never block. See
17
- * `docs/rp4-ontology-boundary-plan-v2.md`.
18
- *
19
- * `SessionOntologyService` (Postgres-backed) persists the touched set and calls
20
- * these helpers; this module keeps the rule itself testable in isolation.
21
- */
22
-
23
- import type { Ontology } from '@bevel-software/platform-shared';
24
-
25
- /** Outcome of a write check against the session's touched-ontology set. */
26
- export type WriteDecision =
27
- | { allow: true }
28
- | { allow: false; reason: 'multi-ontology' | 'different-ontology'; touched: string[]; attempted: string };
29
-
30
- /**
31
- * The set of NAMED ontologies a session has touched after this operation. A
32
- * neutral operation (`ontology === null`) adds nothing. Pure — returns a new
33
- * set, never mutates the input.
34
- */
35
- export function recordTouch(touched: ReadonlySet<string>, ontology: Ontology): Set<string> {
36
- const next = new Set(touched);
37
- if (ontology !== null) next.add(ontology);
38
- return next;
39
- }
40
-
41
- /**
42
- * Decide whether a WRITE to `attempted` is allowed given the ontologies the
43
- * session has already touched (NOT counting this write yet).
44
- *
45
- * - A write to a neutral path (`attempted === null`) is always allowed.
46
- * - If the session has already touched two or more named ontologies, the
47
- * write is blocked (`multi-ontology`) — reading across ontologies poisoned
48
- * the session's write ability.
49
- * - If the session has touched exactly one named ontology and it differs from
50
- * the write target, the write is blocked (`different-ontology`).
51
- * - If the session is unpinned (touched none) or its single touched ontology
52
- * equals the target, the write is allowed.
53
- */
54
- export function decideWrite(touched: ReadonlySet<string>, attempted: Ontology): WriteDecision {
55
- if (attempted === null) return { allow: true };
56
-
57
- // The set of named ontologies the session would span if this write lands.
58
- const spanned = new Set(touched);
59
- spanned.add(attempted);
60
-
61
- if (spanned.size <= 1) return { allow: true };
62
-
63
- // spanned.size >= 2 — the write would put the session across a boundary.
64
- const touchedList = [...touched].sort();
65
- if (touched.size >= 2) {
66
- return { allow: false, reason: 'multi-ontology', touched: touchedList, attempted };
67
- }
68
- // Exactly one prior ontology, and it isn't the write target.
69
- return { allow: false, reason: 'different-ontology', touched: touchedList, attempted };
70
- }