@bevel-software/platform-core-backend 0.21.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.
- package/dist/core/core-ports.d.ts +11 -1
- package/dist/core/core-ports.d.ts.map +1 -1
- package/dist/core/core-ports.js.map +1 -1
- package/dist/core/create-core-server.d.ts +3 -3
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +62 -13
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +22 -3
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +158 -23
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +34 -1
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +89 -13
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/core-config.d.ts +0 -12
- package/dist/core-config.d.ts.map +1 -1
- package/dist/core-config.js +11 -13
- package/dist/core-config.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +42 -12
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +67 -10
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +214 -35
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access-requests.contract.d.ts +75 -0
- package/dist/modules/access/access-requests.contract.d.ts.map +1 -0
- package/dist/modules/access/access-requests.contract.js +20 -0
- package/dist/modules/access/access-requests.contract.js.map +1 -0
- package/dist/modules/access/access-requests.routes.d.ts +35 -0
- package/dist/modules/access/access-requests.routes.d.ts.map +1 -0
- package/dist/modules/access/access-requests.routes.js +237 -0
- package/dist/modules/access/access-requests.routes.js.map +1 -0
- package/dist/modules/access/access-requests.service.d.ts +123 -0
- package/dist/modules/access/access-requests.service.d.ts.map +1 -0
- package/dist/modules/access/access-requests.service.js +337 -0
- package/dist/modules/access/access-requests.service.js.map +1 -0
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +13 -15
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access-model/access-grammar.d.ts +12 -0
- package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
- package/dist/modules/access-model/access-grammar.js +24 -0
- package/dist/modules/access-model/access-grammar.js.map +1 -1
- package/dist/modules/auth/account-admission.d.ts +58 -7
- package/dist/modules/auth/account-admission.d.ts.map +1 -1
- package/dist/modules/auth/account-admission.js +44 -1
- package/dist/modules/auth/account-admission.js.map +1 -1
- package/dist/modules/auth/account.routes.d.ts +1 -1
- package/dist/modules/auth/account.routes.d.ts.map +1 -1
- package/dist/modules/auth/account.routes.js +61 -5
- package/dist/modules/auth/account.routes.js.map +1 -1
- package/dist/modules/auth/auth.middleware.d.ts +1 -1
- package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
- package/dist/modules/auth/auth.middleware.js +18 -7
- package/dist/modules/auth/auth.middleware.js.map +1 -1
- package/dist/modules/auth/auth.routes.d.ts.map +1 -1
- package/dist/modules/auth/auth.routes.js +8 -0
- package/dist/modules/auth/auth.routes.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +96 -3
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +187 -9
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.js +7 -2
- package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +41 -24
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +35 -27
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/github-app/github-app.client.d.ts +101 -0
- package/dist/modules/github-app/github-app.client.d.ts.map +1 -0
- package/dist/modules/github-app/github-app.client.js +222 -0
- package/dist/modules/github-app/github-app.client.js.map +1 -0
- package/dist/modules/github-app/github-app.connection.d.ts +102 -0
- package/dist/modules/github-app/github-app.connection.d.ts.map +1 -0
- package/dist/modules/github-app/github-app.connection.js +185 -0
- package/dist/modules/github-app/github-app.connection.js.map +1 -0
- package/dist/modules/github-app/github-app.routes.d.ts +59 -0
- package/dist/modules/github-app/github-app.routes.d.ts.map +1 -0
- package/dist/modules/github-app/github-app.routes.js +323 -0
- package/dist/modules/github-app/github-app.routes.js.map +1 -0
- package/dist/modules/github-app/index.d.ts +4 -0
- package/dist/modules/github-app/index.d.ts.map +1 -0
- package/dist/modules/github-app/index.js +4 -0
- package/dist/modules/github-app/index.js.map +1 -0
- package/dist/modules/kb-fs/branch-name.d.ts.map +1 -1
- package/dist/modules/kb-fs/branch-name.js +12 -2
- package/dist/modules/kb-fs/branch-name.js.map +1 -1
- package/dist/modules/kb-fs/remote-url.d.ts +22 -0
- package/dist/modules/kb-fs/remote-url.d.ts.map +1 -0
- package/dist/modules/kb-fs/remote-url.js +35 -0
- package/dist/modules/kb-fs/remote-url.js.map +1 -0
- package/dist/modules/kb-fs/repo-path.d.ts +11 -16
- package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
- package/dist/modules/kb-fs/repo-path.js +79 -0
- package/dist/modules/kb-fs/repo-path.js.map +1 -1
- package/dist/modules/kb-sync/kb-sync.routes.d.ts +2 -2
- package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.routes.js +14 -3
- package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -1
- package/dist/modules/kb-sync/sync-auth.d.ts +3 -1
- package/dist/modules/kb-sync/sync-auth.d.ts.map +1 -1
- package/dist/modules/kb-sync/sync-auth.js +1 -1
- package/dist/modules/kb-sync/sync-auth.js.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +21 -6
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +3 -2
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/plugins/join-proposals.d.ts +17 -5
- package/dist/modules/plugins/join-proposals.d.ts.map +1 -1
- package/dist/modules/plugins/join-proposals.js +76 -22
- package/dist/modules/plugins/join-proposals.js.map +1 -1
- package/dist/modules/plugins/join-requests.service.d.ts +111 -18
- package/dist/modules/plugins/join-requests.service.d.ts.map +1 -1
- package/dist/modules/plugins/join-requests.service.js +173 -29
- package/dist/modules/plugins/join-requests.service.js.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +3 -2
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.d.ts +22 -1
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +101 -7
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/settings/managed-repository.d.ts +43 -0
- package/dist/modules/settings/managed-repository.d.ts.map +1 -0
- package/dist/modules/settings/managed-repository.js +60 -0
- package/dist/modules/settings/managed-repository.js.map +1 -0
- package/dist/modules/settings/repository-source.d.ts +128 -0
- package/dist/modules/settings/repository-source.d.ts.map +1 -0
- package/dist/modules/settings/repository-source.js +150 -0
- package/dist/modules/settings/repository-source.js.map +1 -0
- package/dist/modules/settings/setup.routes.d.ts +78 -4
- package/dist/modules/settings/setup.routes.d.ts.map +1 -1
- package/dist/modules/settings/setup.routes.js +379 -17
- package/dist/modules/settings/setup.routes.js.map +1 -1
- package/dist/modules/skills/skill-access-requests.routes.d.ts +6 -8
- package/dist/modules/skills/skill-access-requests.routes.d.ts.map +1 -1
- package/dist/modules/skills/skill-access-requests.routes.js +24 -63
- package/dist/modules/skills/skill-access-requests.routes.js.map +1 -1
- package/dist/modules/skills/skills.contract.d.ts +90 -18
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.contract.js +4 -1
- package/dist/modules/skills/skills.contract.js.map +1 -1
- package/dist/modules/skills/skills.service.d.ts +98 -9
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +239 -37
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts +7 -0
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +71 -7
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +4 -2
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.d.ts +3 -3
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts +16 -7
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +34 -12
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-context.js +15 -0
- package/dist/modules/tool-helpers/tool-context.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts +2 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.js +25 -4
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts +3 -2
- package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
- package/dist/modules/tool-helpers/validate-token.js +1 -1
- package/dist/modules/tool-helpers/validate-token.js.map +1 -1
- package/dist/modules/workflow/agent-tools/change-request-summary.d.ts +98 -0
- package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +1 -0
- package/dist/modules/workflow/agent-tools/change-request-summary.js +81 -0
- package/dist/modules/workflow/agent-tools/change-request-summary.js.map +1 -0
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +113 -37
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/file-lock.service.d.ts +24 -0
- package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
- package/dist/modules/workflow/file-lock.service.js +30 -0
- package/dist/modules/workflow/file-lock.service.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +72 -1
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +170 -6
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -1
- package/dist/modules/workflow/git/node-git-runner.js +5 -0
- package/dist/modules/workflow/git/node-git-runner.js.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.d.ts +45 -0
- package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.js +99 -0
- package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.service.d.ts +38 -0
- package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.service.js +55 -0
- package/dist/modules/workflow/pending-commits.service.js.map +1 -1
- package/dist/modules/workflow/workflow-hooks.d.ts +54 -32
- package/dist/modules/workflow/workflow-hooks.d.ts.map +1 -1
- package/dist/modules/workflow/workflow-hooks.js +16 -1
- package/dist/modules/workflow/workflow-hooks.js.map +1 -1
- package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.routes.js +3 -1
- package/dist/modules/workflow/workflow.routes.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +94 -13
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +258 -51
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/agent-access.gate.d.ts +94 -0
- package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -0
- package/dist/modules/workspace/agent-access.gate.js +123 -0
- package/dist/modules/workspace/agent-access.gate.js.map +1 -0
- package/dist/modules/workspace/routine-write-policy.d.ts +5 -6
- package/dist/modules/workspace/routine-write-policy.d.ts.map +1 -1
- package/dist/modules/workspace/routine-write-policy.js +5 -6
- package/dist/modules/workspace/routine-write-policy.js.map +1 -1
- package/dist/modules/workspace/session-sink.d.ts +5 -5
- package/dist/modules/workspace/set-aside-clone.d.ts +46 -0
- package/dist/modules/workspace/set-aside-clone.d.ts.map +1 -0
- package/dist/modules/workspace/set-aside-clone.js +92 -0
- package/dist/modules/workspace/set-aside-clone.js.map +1 -0
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts +117 -1
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.js +190 -1
- package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +92 -3
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +115 -5
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +272 -26
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +2 -2
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +337 -117
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/modules/write-access/write-access.d.ts +60 -0
- package/dist/modules/write-access/write-access.d.ts.map +1 -0
- package/dist/modules/write-access/write-access.js +129 -0
- package/dist/modules/write-access/write-access.js.map +1 -0
- package/dist/shared/domain-errors.d.ts +64 -0
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +83 -0
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/git.contract.d.ts +30 -0
- package/dist/shared/git.contract.d.ts.map +1 -1
- package/dist/shared/git.contract.js +26 -0
- package/dist/shared/git.contract.js.map +1 -1
- package/dist/tenancy/static-tenant-source.d.ts +0 -1
- package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
- package/dist/tenancy/static-tenant-source.js +0 -2
- package/dist/tenancy/static-tenant-source.js.map +1 -1
- package/migrations/0014_change_request_closed_reason.sql +1 -0
- package/migrations/0015_account_deactivation.sql +3 -0
- package/migrations/meta/0014_snapshot.json +2265 -0
- package/migrations/meta/0015_snapshot.json +2271 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +3 -3
- package/src/__tests__/kb-layout-config.test.ts +0 -2
- package/src/__tests__/retired-settings.test.ts +96 -0
- package/src/core/__tests__/gated-boot.test.ts +132 -0
- package/src/core/__tests__/lifecycle.test.ts +170 -1
- package/src/core/__tests__/set-aside-root-is-one-place.test.ts +63 -0
- package/src/core/core-ports.ts +11 -1
- package/src/core/create-core-server.ts +69 -15
- package/src/core/create-core-services.ts +190 -27
- package/src/core/lifecycle.ts +98 -13
- package/src/core-config.ts +13 -14
- package/src/index.ts +12 -1
- package/src/modules/access/__tests__/access-control.preview-relocation.test.ts +385 -0
- package/src/modules/access/__tests__/access-control.prospective.test.ts +94 -18
- package/src/modules/access/__tests__/access-requests.recut.test.ts +134 -0
- package/src/modules/access/__tests__/access-requests.routes.test.ts +610 -0
- package/src/modules/access/__tests__/access.routes.prospective.test.ts +6 -7
- package/src/modules/access/access-control.interface.ts +43 -12
- package/src/modules/access/access-control.service.ts +234 -36
- package/src/modules/access/access-requests.contract.ts +93 -0
- package/src/modules/access/access-requests.routes.ts +286 -0
- package/src/modules/access/access-requests.service.ts +420 -0
- package/src/modules/access/access.routes.ts +13 -15
- package/src/modules/access-model/access-grammar.ts +22 -0
- package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +3 -1
- package/src/modules/auth/__tests__/account-deactivation.test.ts +252 -0
- package/src/modules/auth/__tests__/account.routes.test.ts +81 -3
- package/src/modules/auth/__tests__/auth.middleware.test.ts +45 -22
- package/src/modules/auth/__tests__/auth.service.test.ts +39 -1
- package/src/modules/auth/account-admission.ts +78 -9
- package/src/modules/auth/account.routes.ts +63 -7
- package/src/modules/auth/auth.middleware.ts +19 -8
- package/src/modules/auth/auth.routes.ts +8 -0
- package/src/modules/auth/auth.service.ts +207 -7
- package/src/modules/auth/oidc-auth-provider.ts +7 -2
- package/src/modules/database/core-schema.ts +35 -27
- package/src/modules/github-app/__tests__/github-app.test.ts +848 -0
- package/src/modules/github-app/github-app.client.ts +268 -0
- package/src/modules/github-app/github-app.connection.ts +204 -0
- package/src/modules/github-app/github-app.routes.ts +359 -0
- package/src/modules/github-app/index.ts +19 -0
- package/src/modules/kb-fs/__tests__/branch-name.test.ts +10 -0
- package/src/modules/kb-fs/__tests__/remote-url.test.ts +38 -0
- package/src/modules/kb-fs/__tests__/repo-path.test.ts +123 -0
- package/src/modules/kb-fs/branch-name.ts +14 -1
- package/src/modules/kb-fs/remote-url.ts +35 -0
- package/src/modules/kb-fs/repo-path.ts +85 -0
- package/src/modules/kb-sync/__tests__/kb-sync.routes.test.ts +26 -1
- package/src/modules/kb-sync/kb-sync.routes.ts +14 -4
- package/src/modules/kb-sync/sync-auth.ts +2 -2
- package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +60 -3
- package/src/modules/mcp/__tests__/mcp.service.test.ts +68 -6
- package/src/modules/mcp/mcp-auth.middleware.ts +20 -6
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +3 -1
- package/src/modules/plugins/__tests__/join-proposals.test.ts +100 -19
- package/src/modules/plugins/__tests__/join-requests.service.test.ts +27 -11
- package/src/modules/plugins/__tests__/join-requests.settlement.test.ts +371 -0
- package/src/modules/plugins/__tests__/plugins.routes.test.ts +1 -1
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +2 -1
- package/src/modules/plugins/join-proposals.ts +87 -19
- package/src/modules/plugins/join-requests.service.ts +199 -39
- package/src/modules/plugins/plugins.routes.ts +3 -2
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +16 -0
- package/src/modules/settings/__tests__/repository-source.test.ts +191 -0
- package/src/modules/settings/__tests__/setup.routes.git-mode.test.ts +386 -0
- package/src/modules/settings/__tests__/setup.routes.github-app.test.ts +299 -0
- package/src/modules/settings/__tests__/setup.routes.managed-phase.test.ts +236 -0
- package/src/modules/settings/__tests__/setup.routes.repository-change.test.ts +390 -0
- package/src/modules/settings/__tests__/setup.routes.test.ts +3 -0
- package/src/modules/settings/deployment-settings.service.ts +117 -6
- package/src/modules/settings/managed-repository.ts +68 -0
- package/src/modules/settings/repository-source.ts +197 -0
- package/src/modules/settings/setup.routes.ts +463 -15
- package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +2 -1
- package/src/modules/skills/__tests__/branch-skills.tools.test.ts +218 -0
- package/src/modules/skills/__tests__/skill-access-requests.routes.test.ts +5 -1
- package/src/modules/skills/__tests__/skills.service.test.ts +229 -5
- package/src/modules/skills/skill-access-requests.routes.ts +25 -75
- package/src/modules/skills/skills.contract.ts +91 -18
- package/src/modules/skills/skills.service.ts +278 -41
- package/src/modules/skills/skills.tools.ts +80 -8
- package/src/modules/tool-auth/__tests__/manual-auth.middleware.test.ts +14 -1
- package/src/modules/tool-auth/external-api-key.service.ts +4 -2
- package/src/modules/tool-auth/internal-token.service.ts +3 -3
- package/src/modules/tool-auth/tool-auth.middleware.ts +34 -11
- package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +5 -3
- package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +3 -3
- package/src/modules/tool-helpers/__tests__/validate-token.test.ts +11 -1
- package/src/modules/tool-helpers/tool-context.ts +15 -0
- package/src/modules/tool-helpers/tool-handler.ts +24 -4
- package/src/modules/tool-helpers/tool.contract.ts +3 -2
- package/src/modules/tool-helpers/validate-token.ts +1 -1
- package/src/modules/workflow/__tests__/apply-failure.test.ts +9 -1
- package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +111 -0
- package/src/modules/workflow/__tests__/workflow.routes.history-read-gate.test.ts +25 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +159 -8
- package/src/modules/workflow/__tests__/workflow.service.repository-replaced.test.ts +249 -0
- package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +146 -40
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +405 -7
- package/src/modules/workflow/agent-tools/change-request-summary.ts +182 -0
- package/src/modules/workflow/agent-tools/workflow.tools.ts +135 -38
- package/src/modules/workflow/file-lock.service.ts +31 -0
- package/src/modules/workflow/git/__tests__/git.service.fileBytesAtCommit.test.ts +260 -0
- package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +121 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.getPrDetail.test.ts +145 -2
- package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-delete.test.ts +166 -0
- package/src/modules/workflow/git/git.service.ts +193 -4
- package/src/modules/workflow/git/node-git-runner.ts +6 -0
- package/src/modules/workflow/git/pull-request.service.ts +119 -0
- package/src/modules/workflow/pending-commits.service.ts +59 -1
- package/src/modules/workflow/workflow-hooks.ts +64 -26
- package/src/modules/workflow/workflow.routes.ts +3 -1
- package/src/modules/workflow/workflow.service.ts +290 -55
- package/src/modules/workspace/__tests__/agent-access.gate.test.ts +208 -0
- package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +384 -0
- package/src/modules/workspace/__tests__/file-stat-access.test.ts +2 -1
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +2 -1
- package/src/modules/workspace/__tests__/set-aside-clone.test.ts +52 -0
- package/src/modules/workspace/__tests__/workspace.routes.at-ref.test.ts +305 -0
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +2 -0
- package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +147 -0
- package/src/modules/workspace/__tests__/workspace.service.replaced-repository.test.ts +326 -0
- package/src/modules/workspace/__tests__/workspace.service.test.ts +20 -9
- package/src/modules/workspace/__tests__/workspace.service.unknown-branch.test.ts +43 -0
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +2 -3
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +235 -10
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +720 -17
- package/src/modules/workspace/agent-access.gate.ts +164 -0
- package/src/modules/workspace/routine-write-policy.ts +5 -6
- package/src/modules/workspace/session-sink.ts +5 -5
- package/src/modules/workspace/set-aside-clone.ts +96 -0
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +297 -0
- package/src/modules/workspace/startup/kb-startup-runner.ts +242 -2
- package/src/modules/workspace/workspace.routes.ts +95 -3
- package/src/modules/workspace/workspace.service.ts +273 -25
- package/src/modules/workspace/workspace.tools.ts +373 -119
- package/src/modules/write-access/__tests__/write-access.test.ts +248 -0
- package/src/modules/write-access/write-access.ts +153 -0
- package/src/shared/domain-errors.ts +89 -0
- package/src/shared/git.contract.ts +37 -0
- package/src/tenancy/__tests__/static-tenant-source.test.ts +0 -1
- package/src/tenancy/static-tenant-source.ts +0 -3
- package/dist/modules/workflow/session-ontology.policy.d.ts +0 -52
- package/dist/modules/workflow/session-ontology.policy.d.ts.map +0 -1
- package/dist/modules/workflow/session-ontology.policy.js +0 -62
- package/dist/modules/workflow/session-ontology.policy.js.map +0 -1
- package/dist/modules/workflow/session-ontology.service.d.ts +0 -105
- package/dist/modules/workflow/session-ontology.service.d.ts.map +0 -1
- package/dist/modules/workflow/session-ontology.service.js +0 -147
- package/dist/modules/workflow/session-ontology.service.js.map +0 -1
- package/dist/modules/workspace/session-ontology.gate.d.ts +0 -114
- package/dist/modules/workspace/session-ontology.gate.d.ts.map +0 -1
- package/dist/modules/workspace/session-ontology.gate.js +0 -161
- package/dist/modules/workspace/session-ontology.gate.js.map +0 -1
- package/dist/shared/kb-layout.d.ts +0 -39
- package/dist/shared/kb-layout.d.ts.map +0 -1
- package/dist/shared/kb-layout.js +0 -103
- package/dist/shared/kb-layout.js.map +0 -1
- package/dist/shared/kb-layout.test.d.ts +0 -2
- package/dist/shared/kb-layout.test.d.ts.map +0 -1
- package/dist/shared/kb-layout.test.js +0 -75
- package/dist/shared/kb-layout.test.js.map +0 -1
- package/src/modules/workflow/__tests__/session-ontology.policy.test.ts +0 -62
- package/src/modules/workflow/__tests__/session-ontology.service.test.ts +0 -201
- package/src/modules/workflow/session-ontology.policy.ts +0 -70
- package/src/modules/workflow/session-ontology.service.ts +0 -183
- package/src/modules/workspace/__tests__/session-ontology.gate.test.ts +0 -239
- package/src/modules/workspace/session-ontology.gate.ts +0 -191
- package/src/shared/kb-layout.test.ts +0 -98
- package/src/shared/kb-layout.ts +0 -102
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { testKbContext } from '../../../__tests__/kb-context.js';
|
|
3
|
+
import { NodeFs } from '../../kb-fs/node-fs.js';
|
|
4
|
+
import { execFile } from 'node:child_process';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import fs from 'node:fs/promises';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import os from 'node:os';
|
|
9
|
+
import { WorkspaceService } from '../workspace.service.js';
|
|
10
|
+
import { NodeGitRunner } from '../../workflow/git/node-git-runner.js';
|
|
11
|
+
|
|
12
|
+
const execFileAsync = promisify(execFile);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A working copy left on disk by a deployment that has since been pointed at
|
|
16
|
+
* a DIFFERENT repository.
|
|
17
|
+
*
|
|
18
|
+
* The workspace service adopts whatever clone it finds on disk, and a clone
|
|
19
|
+
* fetches through the address stored in its own `remote.origin.url` — so an
|
|
20
|
+
* adopted one of the repository that was replaced answers "repository not
|
|
21
|
+
* found" on every fetch from then on, with nothing to re-clone it. It is set
|
|
22
|
+
* aside and cloned fresh instead.
|
|
23
|
+
*
|
|
24
|
+
* SET ASIDE, never deleted — the same rule, and the same destination, as the
|
|
25
|
+
* KB startup phase's sweep. This path has reached no remote of its own: it
|
|
26
|
+
* runs wherever the address is stored but unproven (a gated boot, a
|
|
27
|
+
* break-glass start, a save that changed the address before the phase got to
|
|
28
|
+
* it), so a mistyped host name must not cost anyone commits that exist
|
|
29
|
+
* nowhere else.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const BRANCH = 'main';
|
|
33
|
+
|
|
34
|
+
async function git(cwd: string, args: string[]): Promise<string> {
|
|
35
|
+
const { stdout } = await execFileAsync('git', args, {
|
|
36
|
+
cwd,
|
|
37
|
+
env: {
|
|
38
|
+
...process.env,
|
|
39
|
+
GIT_AUTHOR_NAME: 'Test', GIT_AUTHOR_EMAIL: 't@x.com',
|
|
40
|
+
GIT_COMMITTER_NAME: 'Test', GIT_COMMITTER_EMAIL: 't@x.com',
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
return stdout.toString();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let root: string;
|
|
47
|
+
let workspacesRoot: string;
|
|
48
|
+
|
|
49
|
+
beforeEach(async () => {
|
|
50
|
+
root = await fs.mkdtemp(path.join(os.tmpdir(), 'ws-replaced-'));
|
|
51
|
+
workspacesRoot = path.join(root, 'workspaces');
|
|
52
|
+
await fs.mkdir(workspacesRoot, { recursive: true });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
afterEach(async () => {
|
|
56
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
/** A bare repository with one commit on `main`, whose file says `marker`. */
|
|
60
|
+
async function upstream(name: string, marker: string): Promise<string> {
|
|
61
|
+
const bare = path.join(root, `${name}.git`);
|
|
62
|
+
await git(root, ['init', '--bare', '-b', BRANCH, bare]);
|
|
63
|
+
const seed = path.join(root, `.seed-${name}`);
|
|
64
|
+
await fs.mkdir(seed);
|
|
65
|
+
await git(seed, ['init', '-b', BRANCH]);
|
|
66
|
+
await fs.writeFile(path.join(seed, 'marker.txt'), marker, 'utf8');
|
|
67
|
+
await git(seed, ['add', '-A']);
|
|
68
|
+
await git(seed, ['commit', '-m', 'init']);
|
|
69
|
+
await git(seed, ['remote', 'add', 'origin', bare]);
|
|
70
|
+
await git(seed, ['push', 'origin', BRANCH]);
|
|
71
|
+
return bare;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function service(
|
|
75
|
+
kbRepoUrl: () => string,
|
|
76
|
+
setAsideRoot?: string,
|
|
77
|
+
aroundSetAside?: (workspaceId: string, move: () => Promise<void>) => Promise<void>,
|
|
78
|
+
) {
|
|
79
|
+
return new WorkspaceService(
|
|
80
|
+
workspacesRoot,
|
|
81
|
+
kbRepoUrl,
|
|
82
|
+
testKbContext({ branchModel: { defaultBranch: BRANCH, protectedBranches: [BRANCH] } }),
|
|
83
|
+
new NodeFs(),
|
|
84
|
+
new NodeGitRunner(),
|
|
85
|
+
setAsideRoot,
|
|
86
|
+
aroundSetAside,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const cloneDir = () => path.join(workspacesRoot, encodeURIComponent(BRANCH), 'knowledge-base');
|
|
91
|
+
|
|
92
|
+
/** Every working copy under a set-aside root, as repo directories. */
|
|
93
|
+
async function setAside(setAsideRoot = path.join(root, 'replaced-working-copies')): Promise<string[]> {
|
|
94
|
+
const stamps = await fs.readdir(setAsideRoot).catch(() => [] as string[]);
|
|
95
|
+
const kept: string[] = [];
|
|
96
|
+
for (const stamp of stamps) {
|
|
97
|
+
for (const id of await fs.readdir(path.join(setAsideRoot, stamp))) {
|
|
98
|
+
kept.push(path.join(setAsideRoot, stamp, id));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return kept;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
describe('a working copy of a repository that was replaced', () => {
|
|
105
|
+
it('is set aside and cloned fresh from the configured address', async () => {
|
|
106
|
+
const old = await upstream('old', 'old repository');
|
|
107
|
+
const replacement = await upstream('replacement', 'new repository');
|
|
108
|
+
// A clone of the OLD repository, sitting on disk from before the change.
|
|
109
|
+
await fs.mkdir(path.dirname(cloneDir()), { recursive: true });
|
|
110
|
+
await git(root, ['clone', '-b', BRANCH, old, cloneDir()]);
|
|
111
|
+
|
|
112
|
+
await service(() => replacement).getOrCreateForBranch(BRANCH);
|
|
113
|
+
|
|
114
|
+
expect((await git(cloneDir(), ['config', '--get', 'remote.origin.url'])).trim()).toBe(replacement);
|
|
115
|
+
expect(await fs.readFile(path.join(cloneDir(), 'marker.txt'), 'utf8')).toBe('new repository');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('keeps the work that was only ever committed there, in the set-aside folder', async () => {
|
|
119
|
+
const old = await upstream('old', 'old repository');
|
|
120
|
+
const replacement = await upstream('replacement', 'new repository');
|
|
121
|
+
await fs.mkdir(path.dirname(cloneDir()), { recursive: true });
|
|
122
|
+
await git(root, ['clone', '-b', BRANCH, old, cloneDir()]);
|
|
123
|
+
// Committed here and pushed nowhere. The address may yet turn out to be a
|
|
124
|
+
// typo, so this is the last copy of it in the world.
|
|
125
|
+
await fs.writeFile(path.join(cloneDir(), 'unpushed.md'), 'local work', 'utf8');
|
|
126
|
+
await git(cloneDir(), ['add', '-A']);
|
|
127
|
+
await git(cloneDir(), ['commit', '-m', 'local work']);
|
|
128
|
+
const head = (await git(cloneDir(), ['rev-parse', 'HEAD'])).trim();
|
|
129
|
+
|
|
130
|
+
await service(() => replacement).getOrCreateForBranch(BRANCH);
|
|
131
|
+
|
|
132
|
+
const kept = await setAside();
|
|
133
|
+
expect(kept).toHaveLength(1);
|
|
134
|
+
// Still a repository, still holding the commit, still reachable by hand:
|
|
135
|
+
// `git log` there shows it and `git push <address> <branch>` sends it on.
|
|
136
|
+
expect((await git(kept[0]!, ['rev-parse', 'HEAD'])).trim()).toBe(head);
|
|
137
|
+
expect(await fs.readFile(path.join(kept[0]!, 'unpushed.md'), 'utf8')).toBe('local work');
|
|
138
|
+
// And the fresh clone really is of the new repository.
|
|
139
|
+
expect(await fs.readFile(path.join(cloneDir(), 'marker.txt'), 'utf8')).toBe('new repository');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('leaves a clone of the configured repository exactly where it is', async () => {
|
|
143
|
+
const configured = await upstream('configured', 'configured');
|
|
144
|
+
await fs.mkdir(path.dirname(cloneDir()), { recursive: true });
|
|
145
|
+
await git(root, ['clone', '-b', BRANCH, configured, cloneDir()]);
|
|
146
|
+
// Committed here and never pushed: a needless re-clone would lose it.
|
|
147
|
+
await fs.writeFile(path.join(cloneDir(), 'unpushed.md'), 'local work', 'utf8');
|
|
148
|
+
await git(cloneDir(), ['add', '-A']);
|
|
149
|
+
await git(cloneDir(), ['commit', '-m', 'local work']);
|
|
150
|
+
const head = (await git(cloneDir(), ['rev-parse', 'HEAD'])).trim();
|
|
151
|
+
|
|
152
|
+
await service(() => configured).getOrCreateForBranch(BRANCH);
|
|
153
|
+
|
|
154
|
+
expect((await git(cloneDir(), ['rev-parse', 'HEAD'])).trim()).toBe(head);
|
|
155
|
+
expect(await setAside()).toEqual([]);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('keeps a clone whose address differs only in spelling', async () => {
|
|
159
|
+
const configured = await upstream('configured', 'configured');
|
|
160
|
+
await fs.mkdir(path.dirname(cloneDir()), { recursive: true });
|
|
161
|
+
await git(root, ['clone', '-b', BRANCH, configured, cloneDir()]);
|
|
162
|
+
await fs.writeFile(path.join(cloneDir(), 'unpushed.md'), 'local work', 'utf8');
|
|
163
|
+
await git(cloneDir(), ['add', '-A']);
|
|
164
|
+
await git(cloneDir(), ['commit', '-m', 'local work']);
|
|
165
|
+
const head = (await git(cloneDir(), ['rev-parse', 'HEAD'])).trim();
|
|
166
|
+
|
|
167
|
+
// The same path with a trailing slash — the same repository.
|
|
168
|
+
await service(() => `${configured}/`).getOrCreateForBranch(BRANCH);
|
|
169
|
+
|
|
170
|
+
expect((await git(cloneDir(), ['rev-parse', 'HEAD'])).trim()).toBe(head);
|
|
171
|
+
expect(await setAside()).toEqual([]);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('keeps a clone when the process does not know where the repository is', async () => {
|
|
175
|
+
const configured = await upstream('configured', 'configured');
|
|
176
|
+
await fs.mkdir(path.dirname(cloneDir()), { recursive: true });
|
|
177
|
+
await git(root, ['clone', '-b', BRANCH, configured, cloneDir()]);
|
|
178
|
+
|
|
179
|
+
// Nothing configured yet: "could not tell" is no reason to move work.
|
|
180
|
+
await service(() => '').getOrCreateForBranch(BRANCH);
|
|
181
|
+
|
|
182
|
+
expect((await git(cloneDir(), ['config', '--get', 'remote.origin.url'])).trim()).toBe(configured);
|
|
183
|
+
expect(await setAside()).toEqual([]);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* WHERE it is set aside is not a detail. A deployment runs from an image whose
|
|
189
|
+
* filesystem is replaced on every recreate, and only the mounted volumes
|
|
190
|
+
* survive that — so a working copy kept beside the workspaces root was kept
|
|
191
|
+
* until the next `docker compose up` and no longer, while the screen had
|
|
192
|
+
* promised the admin it was there to recover from. The composition root roots
|
|
193
|
+
* this service and the KB startup phase at ONE directory under the backups
|
|
194
|
+
* volume; this is the half of that the service owes.
|
|
195
|
+
*/
|
|
196
|
+
describe('where a working copy is set aside', () => {
|
|
197
|
+
it('goes to the root it was given, not beside the workspaces root', async () => {
|
|
198
|
+
const old = await upstream('old', 'old repository');
|
|
199
|
+
const replacement = await upstream('replacement', 'new repository');
|
|
200
|
+
await fs.mkdir(path.dirname(cloneDir()), { recursive: true });
|
|
201
|
+
await git(root, ['clone', '-b', BRANCH, old, cloneDir()]);
|
|
202
|
+
await fs.writeFile(path.join(cloneDir(), 'unpushed.md'), 'local work', 'utf8');
|
|
203
|
+
await git(cloneDir(), ['add', '-A']);
|
|
204
|
+
await git(cloneDir(), ['commit', '-m', 'local work']);
|
|
205
|
+
// Somewhere the workspaces root cannot be reached from by walking up:
|
|
206
|
+
// the backups volume, as the deployment mounts it.
|
|
207
|
+
const backups = path.join(root, 'backups', 'replaced-working-copies');
|
|
208
|
+
|
|
209
|
+
await service(() => replacement, backups).getOrCreateForBranch(BRANCH);
|
|
210
|
+
|
|
211
|
+
const kept = await setAside(backups);
|
|
212
|
+
expect(kept).toHaveLength(1);
|
|
213
|
+
expect(await fs.readFile(path.join(kept[0]!, 'unpushed.md'), 'utf8')).toBe('local work');
|
|
214
|
+
// And nothing was left in the place that does not outlive the container.
|
|
215
|
+
expect(await setAside()).toEqual([]);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('falls back beside the workspaces root when no root is given', async () => {
|
|
219
|
+
const old = await upstream('old', 'old repository');
|
|
220
|
+
const replacement = await upstream('replacement', 'new repository');
|
|
221
|
+
await fs.mkdir(path.dirname(cloneDir()), { recursive: true });
|
|
222
|
+
await git(root, ['clone', '-b', BRANCH, old, cloneDir()]);
|
|
223
|
+
|
|
224
|
+
await service(() => replacement).getOrCreateForBranch(BRANCH);
|
|
225
|
+
|
|
226
|
+
expect(await setAside()).toHaveLength(1);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* A branch opened onto a clone of another repository sets it aside on the
|
|
232
|
+
* spot, with nobody having stopped anything for it. What the deployment does
|
|
233
|
+
* around the startup phase's set-asides (the commit worker held, the copy's
|
|
234
|
+
* queued commits held back, its locks dropped) it is handed here to do around
|
|
235
|
+
* this one.
|
|
236
|
+
*/
|
|
237
|
+
describe('what surrounds the setting aside of one working copy', () => {
|
|
238
|
+
async function onTheOldRepository() {
|
|
239
|
+
const old = await upstream('old', 'old repository');
|
|
240
|
+
const replacement = await upstream('replacement', 'new repository');
|
|
241
|
+
await fs.mkdir(path.dirname(cloneDir()), { recursive: true });
|
|
242
|
+
await git(root, ['clone', '-b', BRANCH, old, cloneDir()]);
|
|
243
|
+
return { old, replacement };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
it('is given the working copy and the move, and the copy is where it was until the move is run', async () => {
|
|
247
|
+
const { old, replacement } = await onTheOldRepository();
|
|
248
|
+
const seen: string[] = [];
|
|
249
|
+
const svc = service(() => replacement, undefined, async (workspaceId, move) => {
|
|
250
|
+
seen.push(`before ${workspaceId}: ${(await git(cloneDir(), ['config', '--get', 'remote.origin.url'])).trim()}`);
|
|
251
|
+
await move();
|
|
252
|
+
seen.push(`after: ${await fs.access(cloneDir()).then(() => 'still there', () => 'gone')}`);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
await svc.getOrCreateForBranch(BRANCH);
|
|
256
|
+
|
|
257
|
+
expect(seen).toEqual([`before ${encodeURIComponent(BRANCH)}: ${old}`, 'after: gone']);
|
|
258
|
+
expect(await fs.readFile(path.join(cloneDir(), 'marker.txt'), 'utf8')).toBe('new repository');
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('refuses the branch, and leaves the copy where it was, when what must happen first fails', async () => {
|
|
262
|
+
const { old, replacement } = await onTheOldRepository();
|
|
263
|
+
const svc = service(() => replacement, undefined, async () => {
|
|
264
|
+
throw new Error('the queue could not be held back');
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
await expect(svc.getOrCreateForBranch(BRANCH)).rejects.toThrow('the queue could not be held back');
|
|
268
|
+
|
|
269
|
+
expect((await git(cloneDir(), ['config', '--get', 'remote.origin.url'])).trim()).toBe(old);
|
|
270
|
+
expect(await setAside()).toEqual([]);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* What surrounds the move can wait: for the commit in flight, or for the
|
|
275
|
+
* startup phase, which may set this very copy aside in the meantime.
|
|
276
|
+
*/
|
|
277
|
+
it('moves nothing, and still opens the branch, when the startup phase took the copy while it waited', async () => {
|
|
278
|
+
const { replacement } = await onTheOldRepository();
|
|
279
|
+
const phaseKept = path.join(root, 'kept-by-the-phase');
|
|
280
|
+
const svc: WorkspaceService = service(() => replacement, undefined, async (workspaceId, move) => {
|
|
281
|
+
// The phase, while this call waited: the copy moved out, and said so.
|
|
282
|
+
await fs.rename(cloneDir(), phaseKept);
|
|
283
|
+
svc.forgetClone(workspaceId);
|
|
284
|
+
await move();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
await svc.getOrCreateForBranch(BRANCH);
|
|
288
|
+
|
|
289
|
+
expect(await fs.readFile(path.join(cloneDir(), 'marker.txt'), 'utf8')).toBe('new repository');
|
|
290
|
+
// Nothing of this call's own was set aside: the phase's copy is the only one.
|
|
291
|
+
expect(await setAside()).toEqual([]);
|
|
292
|
+
expect(await fs.readFile(path.join(phaseKept, 'marker.txt'), 'utf8')).toBe('old repository');
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('moves it once for two callers opening the branch together, and serves both', async () => {
|
|
296
|
+
const { replacement } = await onTheOldRepository();
|
|
297
|
+
let moves = 0;
|
|
298
|
+
const svc = service(() => replacement, undefined, async (_workspaceId, move) => {
|
|
299
|
+
moves += 1;
|
|
300
|
+
await move();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const [first, second] = await Promise.all([svc.getOrCreateForBranch(BRANCH), svc.getOrCreateForBranch(BRANCH)]);
|
|
304
|
+
|
|
305
|
+
expect(moves).toBe(1);
|
|
306
|
+
expect(first.repoDir).toBe(second.repoDir);
|
|
307
|
+
expect(await setAside()).toHaveLength(1);
|
|
308
|
+
expect(await fs.readFile(path.join(cloneDir(), 'marker.txt'), 'utf8')).toBe('new repository');
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
describe('forgetClone', () => {
|
|
313
|
+
it('drops the cached directory, so the next open clones again', async () => {
|
|
314
|
+
const configured = await upstream('configured', 'configured');
|
|
315
|
+
const svc = service(() => configured);
|
|
316
|
+
await svc.getOrCreateForBranch(BRANCH);
|
|
317
|
+
// What the KB startup phase does to a clone of another repository: moves
|
|
318
|
+
// it out of the workspaces root and says so.
|
|
319
|
+
await fs.rm(cloneDir(), { recursive: true, force: true });
|
|
320
|
+
svc.forgetClone(encodeURIComponent(BRANCH));
|
|
321
|
+
|
|
322
|
+
await svc.getOrCreateForBranch(BRANCH);
|
|
323
|
+
|
|
324
|
+
expect(await fs.readFile(path.join(cloneDir(), 'marker.txt'), 'utf8')).toBe('configured');
|
|
325
|
+
});
|
|
326
|
+
});
|
|
@@ -1122,7 +1122,7 @@ describe('WorkspaceService.readAllKbFiles', () => {
|
|
|
1122
1122
|
});
|
|
1123
1123
|
});
|
|
1124
1124
|
|
|
1125
|
-
describe('WorkspaceService.unzipFile —
|
|
1125
|
+
describe('WorkspaceService.unzipFile — per-entry write guard', () => {
|
|
1126
1126
|
let root: string;
|
|
1127
1127
|
let svc: WorkspaceService;
|
|
1128
1128
|
let workspaceDir: string;
|
|
@@ -1154,12 +1154,12 @@ describe('WorkspaceService.unzipFile — ontology-session write guard', () => {
|
|
|
1154
1154
|
it('skips entries the write guard rejects and never writes them to disk', async () => {
|
|
1155
1155
|
await writeZip('a.zip', { 'keep.md': 'ok', 'blocked.md': 'no' });
|
|
1156
1156
|
const res = await svc.unzipFile(workspaceId, 'a.zip', 'out', async (wsPath) => {
|
|
1157
|
-
if (wsPath.endsWith('blocked.md')) throw new Error('
|
|
1157
|
+
if (wsPath.endsWith('blocked.md')) throw new Error('Not in this conversation.');
|
|
1158
1158
|
});
|
|
1159
1159
|
// An unprefixed destination is the repository's own `out/`, and the
|
|
1160
1160
|
// extracted paths are reported workspace-relative, so prefixed.
|
|
1161
1161
|
expect(res.extracted).toEqual(['knowledge-base/out/keep.md']);
|
|
1162
|
-
expect(res.skipped).toContainEqual({ path: 'blocked.md', reason: '
|
|
1162
|
+
expect(res.skipped).toContainEqual({ path: 'blocked.md', reason: 'Not in this conversation.' });
|
|
1163
1163
|
expect((await fs.readFile(path.join(repoDir, 'out', 'keep.md'))).toString()).toBe('ok');
|
|
1164
1164
|
await expect(fs.readFile(path.join(repoDir, 'out', 'blocked.md'))).rejects.toThrow();
|
|
1165
1165
|
await expect(fs.stat(path.join(workspaceDir, 'out'))).rejects.toMatchObject({ code: 'ENOENT' });
|
|
@@ -1173,18 +1173,18 @@ describe('WorkspaceService.unzipFile — ontology-session write guard', () => {
|
|
|
1173
1173
|
await fs.writeFile(path.join(repoDir, 'c.zip'), zip.toBuffer());
|
|
1174
1174
|
|
|
1175
1175
|
const res = await svc.unzipFile(workspaceId, 'c.zip', 'out', async (wsPath) => {
|
|
1176
|
-
if (wsPath.includes('blocked-dir')) throw new Error('
|
|
1176
|
+
if (wsPath.includes('blocked-dir')) throw new Error('Not in this conversation.');
|
|
1177
1177
|
});
|
|
1178
1178
|
|
|
1179
1179
|
expect(res.extracted).toEqual(['knowledge-base/out/keep.md']);
|
|
1180
|
-
expect(res.skipped).toContainEqual({ path: 'blocked-dir/', reason: '
|
|
1180
|
+
expect(res.skipped).toContainEqual({ path: 'blocked-dir/', reason: 'Not in this conversation.' });
|
|
1181
1181
|
await expect(fs.stat(path.join(repoDir, 'out', 'blocked-dir'))).rejects.toThrow();
|
|
1182
1182
|
});
|
|
1183
1183
|
|
|
1184
1184
|
it('does not create the destination directory when every entry is blocked', async () => {
|
|
1185
1185
|
await writeZip('d.zip', { 'a.md': '1', 'b.md': '2' });
|
|
1186
1186
|
const res = await svc.unzipFile(workspaceId, 'd.zip', 'out', async () => {
|
|
1187
|
-
throw new Error('
|
|
1187
|
+
throw new Error('Not in this conversation.');
|
|
1188
1188
|
});
|
|
1189
1189
|
expect(res.extracted).toEqual([]);
|
|
1190
1190
|
expect(res.skipped).toHaveLength(2);
|
|
@@ -1213,11 +1213,22 @@ describe('WorkspaceService.unzipFile — ontology-session write guard', () => {
|
|
|
1213
1213
|
'There is no file or directory at "knowledge-base/nope.zip" in this workspace. Check the path with list_files.',
|
|
1214
1214
|
});
|
|
1215
1215
|
// A name carrying a line break cannot forge a second line of the answer,
|
|
1216
|
-
// in the message or in the `path` a JSON consumer reads.
|
|
1217
|
-
|
|
1216
|
+
// in the message or in the `path` a JSON consumer reads. A LINE BREAK no
|
|
1217
|
+
// longer reaches this refusal at all — the normaliser now refuses a
|
|
1218
|
+
// control character outright, because git reads one in a path as the end
|
|
1219
|
+
// of an object name (see `repo-path.ts`) — so the forged name here
|
|
1220
|
+
// carries U+2028, which is a line break to a renderer and nothing to git:
|
|
1221
|
+
// still accepted as a file name, still escaped on the way out.
|
|
1222
|
+
const forged = 'a\u2028c.zip';
|
|
1218
1223
|
await expect(svc.unzipFile(workspaceId, forged)).rejects.toMatchObject({
|
|
1219
1224
|
status: 404,
|
|
1220
|
-
payload: { kind: 'not_found', path: 'knowledge-base/a\\
|
|
1225
|
+
payload: { kind: 'not_found', path: 'knowledge-base/a\\u2028c.zip' },
|
|
1226
|
+
});
|
|
1227
|
+
// And the same invariant at the gate that now owns the line break: the
|
|
1228
|
+
// refusal names the path with the break escaped, never raw.
|
|
1229
|
+
await expect(svc.unzipFile(workspaceId, 'a\nb.zip')).rejects.toMatchObject({
|
|
1230
|
+
status: 400,
|
|
1231
|
+
payload: { kind: 'path-control-character', path: 'a\\nb.zip' },
|
|
1221
1232
|
});
|
|
1222
1233
|
});
|
|
1223
1234
|
|
|
@@ -77,6 +77,49 @@ describe('WorkspaceService — a branch origin does not have', () => {
|
|
|
77
77
|
});
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* The second defect on this ticket. A bootstrap that fails to clone used to
|
|
82
|
+
* leave its workspace directory behind, and a directory named after a branch
|
|
83
|
+
* is one of the ways the platform remembers having KNOWN a branch. So the
|
|
84
|
+
* first attempt at a typo answered 404 and then quietly created the evidence
|
|
85
|
+
* that turned every later attempt into 410 — the reader was told a branch
|
|
86
|
+
* they had invented "no longer exists on the remote".
|
|
87
|
+
*/
|
|
88
|
+
it('leaves nothing on disk when the clone fails, so a second attempt is still a 404', async () => {
|
|
89
|
+
const svc = service();
|
|
90
|
+
|
|
91
|
+
await expect(svc.getOrCreateForBranch('nobody/never-made-this')).rejects.toMatchObject({
|
|
92
|
+
name: 'BranchNotFoundError',
|
|
93
|
+
status: 404,
|
|
94
|
+
});
|
|
95
|
+
// The failed bootstrap rolled back completely — not the clone directory,
|
|
96
|
+
// not the workspace shell around it.
|
|
97
|
+
expect(await fs.readdir(workspacesRoot)).toEqual([]);
|
|
98
|
+
|
|
99
|
+
// The same name again, on the same instance that has now already failed on
|
|
100
|
+
// it once. Nothing about the first failure may change this answer.
|
|
101
|
+
await expect(svc.getOrCreateForBranch('nobody/never-made-this')).rejects.toMatchObject({
|
|
102
|
+
name: 'BranchNotFoundError',
|
|
103
|
+
status: 404,
|
|
104
|
+
message: 'There is no branch named nobody/never-made-this.',
|
|
105
|
+
payload: { kind: 'branch-not-found', branch: 'nobody/never-made-this' },
|
|
106
|
+
});
|
|
107
|
+
expect(await fs.readdir(workspacesRoot)).toEqual([]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('does not count a bare directory with no clone in it as a branch it knew', async () => {
|
|
111
|
+
// A shell left by an older process (one that crashed mid-bootstrap, or that
|
|
112
|
+
// predates the rollback above). The "have I heard of this branch" probe asks
|
|
113
|
+
// for a COMPLETED clone, so a directory with no `.git` under it is absence:
|
|
114
|
+
// the honest 404 survives whatever is lying around the workspaces root.
|
|
115
|
+
await fs.mkdir(path.join(workspacesRoot, workspaceIdForBranch('nobody/never-made-this')), { recursive: true });
|
|
116
|
+
|
|
117
|
+
await expect(service().getOrCreateForBranch('nobody/never-made-this')).rejects.toMatchObject({
|
|
118
|
+
name: 'BranchNotFoundError',
|
|
119
|
+
status: 404,
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
80
123
|
it('a name a listing showed us, and origin no longer has, stays a 410', async () => {
|
|
81
124
|
const svc = service();
|
|
82
125
|
// What the branch selector saw while the draft still existed.
|
|
@@ -12,6 +12,7 @@ import { DocExtractService } from '../file-readers/doc-extract.service.js';
|
|
|
12
12
|
import { RoutineWritePolicyService } from '../routine-write-policy.js';
|
|
13
13
|
import { SpillStore } from '../spill-store.js';
|
|
14
14
|
import { WorkflowHooks } from '../../workflow/workflow-hooks.js';
|
|
15
|
+
import { ToolDescriptionNotes } from '../../workspace/agent-access.gate.js';
|
|
15
16
|
import { registerWorkspaceTools } from '../workspace.tools.js';
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -42,11 +43,9 @@ async function descriptions(registry: ToolRegistry = new ToolRegistry()): Promis
|
|
|
42
43
|
{} as unknown as IAccessControl,
|
|
43
44
|
kb,
|
|
44
45
|
{
|
|
45
|
-
service: {} as never,
|
|
46
|
-
enabled: false,
|
|
47
|
-
kb,
|
|
48
46
|
recoveryBotEmail: 'recovery-bot@bevel.local',
|
|
49
47
|
hooks: new WorkflowHooks(),
|
|
48
|
+
notes: new ToolDescriptionNotes(),
|
|
50
49
|
},
|
|
51
50
|
new RoutineWritePolicyService(),
|
|
52
51
|
{} as never,
|