@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,218 @@
|
|
|
1
|
+
import type { Server as HttpServer } from 'node:http';
|
|
2
|
+
import express from 'express';
|
|
3
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
4
|
+
import { ToolRegistry } from '../../tool-registry/tool-registry.js';
|
|
5
|
+
import { createToolHandlerFactory } from '../../tool-helpers/tool-handler.js';
|
|
6
|
+
import type { ToolContext } from '../../tool-helpers/tool.contract.js';
|
|
7
|
+
import type { ToolAuth } from '../../tool-auth/tool-auth.middleware.js';
|
|
8
|
+
import { BranchNotFoundError } from '../../../shared/domain-errors.js';
|
|
9
|
+
import { registerSkillsTools } from '../skills.tools.js';
|
|
10
|
+
import { unmergedSkillNotice } from '../skills.service.js';
|
|
11
|
+
import type { GetSkillOptions, ISkillService, ListSkillsOptions, Skill } from '../skills.contract.js';
|
|
12
|
+
import { AllowedToolsChecker } from '../allowed-tools-check.js';
|
|
13
|
+
import type { ToolManualDetail, ToolManualSummary } from '../../tool-manuals/tool-manuals.contract.js';
|
|
14
|
+
import { testKbContext } from '../../../__tests__/kb-context.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The two skill tools reading a draft branch: the input reaches the service,
|
|
18
|
+
* the branch's own answer comes back with its mark and its first line, the
|
|
19
|
+
* `allowed-tools` warnings are computed from the BRANCH's version of the
|
|
20
|
+
* skill, a branch nobody pushed is the file tools' 404, and `branch` with
|
|
21
|
+
* `version` is refused rather than silently ranked.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const DRAFT = 'juan/skill-deck';
|
|
25
|
+
const KB_DIR = 'knowledge-base';
|
|
26
|
+
|
|
27
|
+
const hubspot: ToolManualSummary = { slug: 'hubspot', name: 'hubspot', path: 'Plugins/Sales/hubspot.tool', type: 'inline' };
|
|
28
|
+
const checker = new AllowedToolsChecker(
|
|
29
|
+
{ listExternal: async () => [] },
|
|
30
|
+
{
|
|
31
|
+
listAccessible: async () => [hubspot],
|
|
32
|
+
getDetail: async (): Promise<ToolManualDetail> => ({
|
|
33
|
+
...hubspot,
|
|
34
|
+
description: null,
|
|
35
|
+
capabilities: [{ name: 'search', description: null }],
|
|
36
|
+
}),
|
|
37
|
+
},
|
|
38
|
+
testKbContext({ kbDirName: KB_DIR }),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
/** The released skill and the draft's changed copy of it — only the draft names a tool nobody serves. */
|
|
42
|
+
const released: Skill = {
|
|
43
|
+
name: 'rfi',
|
|
44
|
+
description: 'RFI.',
|
|
45
|
+
path: 'Plugins/Sales/rfi',
|
|
46
|
+
body: '# RFI',
|
|
47
|
+
files: [],
|
|
48
|
+
allowedTools: ['hubspot.search'],
|
|
49
|
+
};
|
|
50
|
+
const onDraft: Skill = {
|
|
51
|
+
...released,
|
|
52
|
+
body: `${unmergedSkillNotice(DRAFT)}\n\n# RFI, rewritten`,
|
|
53
|
+
allowedTools: ['hubspot.search', 'hubspot.serch'],
|
|
54
|
+
unmerged: true,
|
|
55
|
+
branch: DRAFT,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** What the tools handed the service, so a test can see the branch arrive (or not). */
|
|
59
|
+
let listArgs: [string | undefined, ListSkillsOptions | undefined][] = [];
|
|
60
|
+
let getArgs: [string, string, string | undefined, GetSkillOptions | undefined][] = [];
|
|
61
|
+
|
|
62
|
+
const skillService: ISkillService = {
|
|
63
|
+
listSkills: async (userEmail?: string, options?: ListSkillsOptions) => {
|
|
64
|
+
listArgs.push([userEmail, options]);
|
|
65
|
+
if (options?.branch === 'juan/typo') throw new BranchNotFoundError('juan/typo');
|
|
66
|
+
if (options?.branch === DRAFT) {
|
|
67
|
+
return [
|
|
68
|
+
{ name: 'rfi', description: 'RFI.', path: released.path, unmerged: true, branch: DRAFT },
|
|
69
|
+
{ name: 'make-deck', description: 'Decks.', path: 'Plugins/make-deck', unmerged: true, branch: DRAFT },
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
return [{ name: 'rfi', description: 'RFI.', path: released.path }];
|
|
73
|
+
},
|
|
74
|
+
getSkill: async (userEmail: string, name: string, file?: string, options?: GetSkillOptions) => {
|
|
75
|
+
getArgs.push([userEmail, name, file, options]);
|
|
76
|
+
if (options?.branch === 'juan/typo') throw new BranchNotFoundError('juan/typo');
|
|
77
|
+
return { ok: true, kind: 'skill', skill: options?.branch === DRAFT ? onDraft : released };
|
|
78
|
+
},
|
|
79
|
+
invalidate: () => {},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
let httpServer: HttpServer | undefined;
|
|
83
|
+
let registry: ToolRegistry;
|
|
84
|
+
|
|
85
|
+
async function start(): Promise<string> {
|
|
86
|
+
listArgs = [];
|
|
87
|
+
getArgs = [];
|
|
88
|
+
registry = new ToolRegistry();
|
|
89
|
+
const resolve = async (auth: ToolAuth, signal: AbortSignal): Promise<ToolContext> =>
|
|
90
|
+
({
|
|
91
|
+
user: { id: 'u', email: 'e@x', name: 'N' },
|
|
92
|
+
scope: auth.scope,
|
|
93
|
+
source: auth.source,
|
|
94
|
+
abortSignal: signal,
|
|
95
|
+
workspaceService: {} as never,
|
|
96
|
+
workflowService: {} as never,
|
|
97
|
+
events: {} as never,
|
|
98
|
+
getFilesystem: async () => ({}) as never,
|
|
99
|
+
}) as unknown as ToolContext;
|
|
100
|
+
const fakeAuth = (req: express.Request, _res: express.Response, next: express.NextFunction) => {
|
|
101
|
+
req.toolAuth = { source: 'external', userId: 'u', scope: 'read' };
|
|
102
|
+
next();
|
|
103
|
+
};
|
|
104
|
+
const app = express();
|
|
105
|
+
app.use(express.json());
|
|
106
|
+
const router = express.Router();
|
|
107
|
+
registerSkillsTools(registry, router, fakeAuth, createToolHandlerFactory(resolve), skillService, checker);
|
|
108
|
+
app.use('/api', router);
|
|
109
|
+
httpServer = await new Promise<HttpServer>((r) => {
|
|
110
|
+
const s = app.listen(0, () => r(s));
|
|
111
|
+
});
|
|
112
|
+
return `http://127.0.0.1:${(httpServer.address() as { port: number }).port}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const post = (url: string, body: unknown) =>
|
|
116
|
+
fetch(url, {
|
|
117
|
+
method: 'POST',
|
|
118
|
+
headers: { 'content-type': 'application/json', authorization: 'Bearer x' },
|
|
119
|
+
body: JSON.stringify(body),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
afterEach(async () => {
|
|
123
|
+
if (httpServer) await new Promise<void>((r) => httpServer!.close(() => r()));
|
|
124
|
+
httpServer = undefined;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('the skill tools read a branch when asked', () => {
|
|
128
|
+
it('declares `branch` as an OPTIONAL input on both tools', async () => {
|
|
129
|
+
await start();
|
|
130
|
+
const defs = await registry.listExternal({ userEmail: 'e@x' });
|
|
131
|
+
for (const name of ['list_skills', 'get_skill']) {
|
|
132
|
+
const def = defs.find((t) => t.name === name)!;
|
|
133
|
+
// `toolDef` wraps a tool's flat inputs under `body`.
|
|
134
|
+
const inputs = (def.inputs as { properties: { body: { properties: Record<string, unknown>; required?: string[] } } })
|
|
135
|
+
.properties.body;
|
|
136
|
+
expect(inputs.properties.branch).toBeDefined();
|
|
137
|
+
expect(inputs.required ?? []).not.toContain('branch');
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('list_skills passes the branch on and returns what that branch has, marked', async () => {
|
|
142
|
+
const base = await start();
|
|
143
|
+
const body = (await (await post(`${base}/api/agent/tools/list_skills`, { branch: DRAFT })).json()) as {
|
|
144
|
+
skills: { name: string; unmerged?: boolean; branch?: string }[];
|
|
145
|
+
};
|
|
146
|
+
expect(listArgs).toEqual([['e@x', { branch: DRAFT }]]);
|
|
147
|
+
expect(body.skills.map((s) => s.name)).toEqual(['rfi', 'make-deck']);
|
|
148
|
+
expect(body.skills.every((s) => s.unmerged === true && s.branch === DRAFT)).toBe(true);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('list_skills without a branch asks for the released catalog', async () => {
|
|
152
|
+
const base = await start();
|
|
153
|
+
const body = (await (await post(`${base}/api/agent/tools/list_skills`, {})).json()) as {
|
|
154
|
+
skills: { name: string; unmerged?: boolean }[];
|
|
155
|
+
};
|
|
156
|
+
expect(listArgs).toEqual([['e@x', { branch: undefined }]]);
|
|
157
|
+
expect(body.skills).toEqual([{ name: 'rfi', description: 'RFI.', path: released.path }]);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('a blank branch is no branch at all: the released catalog answers', async () => {
|
|
161
|
+
const base = await start();
|
|
162
|
+
await post(`${base}/api/agent/tools/list_skills`, { branch: ' ' });
|
|
163
|
+
await post(`${base}/api/agent/tools/get_skill`, { name: 'rfi', branch: '' });
|
|
164
|
+
expect(listArgs).toEqual([['e@x', { branch: undefined }]]);
|
|
165
|
+
expect(getArgs[0][3]).toEqual({ version: undefined, branch: undefined });
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('get_skill passes the branch on, and the body keeps its unmerged first line', async () => {
|
|
169
|
+
const base = await start();
|
|
170
|
+
const body = (await (await post(`${base}/api/agent/tools/get_skill`, { name: 'rfi', branch: DRAFT })).json()) as {
|
|
171
|
+
skill: { body: string; unmerged?: boolean; branch?: string };
|
|
172
|
+
};
|
|
173
|
+
expect(getArgs).toEqual([['e@x', 'rfi', undefined, { version: undefined, branch: DRAFT }]]);
|
|
174
|
+
expect(body.skill.body.split('\n')[0]).toBe(unmergedSkillNotice(DRAFT));
|
|
175
|
+
expect(body.skill.unmerged).toBe(true);
|
|
176
|
+
expect(body.skill.branch).toBe(DRAFT);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('the allowed-tools check runs on the BRANCH version of the skill', async () => {
|
|
180
|
+
const base = await start();
|
|
181
|
+
const onBranch = (await (
|
|
182
|
+
await post(`${base}/api/agent/tools/get_skill`, { name: 'rfi', branch: DRAFT })
|
|
183
|
+
).json()) as { warnings: { entry: string; suggestion?: string }[] };
|
|
184
|
+
// `hubspot.serch` is only in the DRAFT's `allowed-tools`: the warning can
|
|
185
|
+
// only come from the copy the branch serves.
|
|
186
|
+
expect(onBranch.warnings.map((w) => w.entry)).toEqual(['hubspot.serch']);
|
|
187
|
+
expect(onBranch.warnings[0].suggestion).toBe('hubspot.search');
|
|
188
|
+
const releasedRes = (await (await post(`${base}/api/agent/tools/get_skill`, { name: 'rfi' })).json()) as {
|
|
189
|
+
warnings: unknown[];
|
|
190
|
+
};
|
|
191
|
+
expect(releasedRes.warnings).toEqual([]);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('refuses `branch` together with `version`, naming both, without asking the service', async () => {
|
|
195
|
+
const base = await start();
|
|
196
|
+
const res = await post(`${base}/api/agent/tools/get_skill`, { name: 'rfi', branch: DRAFT, version: '1.4.0' });
|
|
197
|
+
expect(res.status).toBe(400);
|
|
198
|
+
const body = (await res.json()) as { error: string };
|
|
199
|
+
expect(body.error).toContain('`branch` or `version`, not both');
|
|
200
|
+
expect(getArgs).toEqual([]);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('answers 404 naming a branch that does not exist, on both tools', async () => {
|
|
204
|
+
const base = await start();
|
|
205
|
+
for (const [tool, args] of [
|
|
206
|
+
['list_skills', { branch: 'juan/typo' }],
|
|
207
|
+
['get_skill', { name: 'rfi', branch: 'juan/typo' }],
|
|
208
|
+
] as const) {
|
|
209
|
+
const res = await post(`${base}/api/agent/tools/${tool}`, args);
|
|
210
|
+
expect(res.status).toBe(404);
|
|
211
|
+
expect(await res.json()).toMatchObject({
|
|
212
|
+
kind: 'branch-not-found',
|
|
213
|
+
branch: 'juan/typo',
|
|
214
|
+
error: 'There is no branch named juan/typo.',
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
});
|
|
@@ -27,15 +27,19 @@ const FOLDER = 'Skills/Eng/deploy';
|
|
|
27
27
|
|
|
28
28
|
async function makeHarness(listBranches: IWorkflowService['listBranches']) {
|
|
29
29
|
const workflow = {
|
|
30
|
-
|
|
30
|
+
listChangeRequests: vi.fn(async () => []),
|
|
31
31
|
listBranches: vi.fn(listBranches),
|
|
32
32
|
createBranch: vi.fn(async () => ({ name: 'x', isDefault: false, isProtected: false })),
|
|
33
|
+
deleteBranch: vi.fn(async () => undefined),
|
|
33
34
|
commitChanges: vi.fn(async () => null),
|
|
34
35
|
openChangeRequest: vi.fn(async () => ({ number: 42 })),
|
|
36
|
+
getChangeRequestDetail: vi.fn(async () => ({ body: '' })),
|
|
35
37
|
} as unknown as IWorkflowService;
|
|
36
38
|
const workspaceService = {
|
|
37
39
|
getOrCreateForBranch: vi.fn(async (branch: string) => ({ id: workspaceIdForBranch(branch) })),
|
|
38
40
|
readFile: vi.fn(async () => '---\n---\nwrite:\n - Eve <eve@x.io>\n'),
|
|
41
|
+
readFileAtRef: vi.fn(async () => null),
|
|
42
|
+
ensureRemotesFetched: vi.fn(async () => undefined),
|
|
39
43
|
writeFile: vi.fn(async () => undefined),
|
|
40
44
|
} as unknown as WorkspaceService;
|
|
41
45
|
const accessControl = { canWrite: vi.fn(async () => false) } as unknown as IAccessControl;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { describe, test, expect, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
import { NodeFs } from '../../kb-fs/node-fs.js';
|
|
3
|
-
import { mkdtemp, rm, mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { mkdtemp, rm, mkdir, writeFile, cp } from 'node:fs/promises';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { DEFAULT_BRANCH } from '@bevel-software/platform-shared';
|
|
7
|
-
import { SkillService } from '../skills.service.js';
|
|
7
|
+
import { SkillService, unmergedSkillNotice } from '../skills.service.js';
|
|
8
|
+
import { BranchNotFoundError } from '../../../shared/domain-errors.js';
|
|
8
9
|
import { workspaceIdForBranch } from '../../../shared/workspace-id.js';
|
|
9
10
|
import type { WorkspaceService } from '../../workspace/workspace.service.js';
|
|
10
11
|
import type { IAccessControl } from '../../access/access-control.interface.js';
|
|
@@ -38,11 +39,20 @@ You answer RFIs.
|
|
|
38
39
|
|
|
39
40
|
describe('SkillService', () => {
|
|
40
41
|
let root: string;
|
|
42
|
+
/**
|
|
43
|
+
* The branches that have a clone under `root`. A name that is not in here
|
|
44
|
+
* answers the way the real service does for a branch nobody ever pushed: the
|
|
45
|
+
* 404 the file tools answer, not an empty workspace.
|
|
46
|
+
*/
|
|
47
|
+
let clones: Set<string>;
|
|
41
48
|
|
|
42
|
-
// WorkspaceService stub:
|
|
43
|
-
// KB clone at <root>/<
|
|
49
|
+
// WorkspaceService stub: a branch's workspace lives at <root>/<branch's id>,
|
|
50
|
+
// its KB clone at <root>/<id>/<KB_DIR>.
|
|
44
51
|
const workspaceService = {
|
|
45
|
-
getOrCreateForBranch: async () =>
|
|
52
|
+
getOrCreateForBranch: async (branch: string) => {
|
|
53
|
+
if (!clones.has(branch)) throw new BranchNotFoundError(branch);
|
|
54
|
+
return { id: workspaceIdForBranch(branch) };
|
|
55
|
+
},
|
|
46
56
|
getWorkspacePath: async (id: string) => join(root, id),
|
|
47
57
|
readFile: async (id: string, rel: string) => fsReadFile(join(root, id, rel), 'utf-8'),
|
|
48
58
|
} as unknown as WorkspaceService;
|
|
@@ -58,6 +68,7 @@ describe('SkillService', () => {
|
|
|
58
68
|
|
|
59
69
|
beforeEach(async () => {
|
|
60
70
|
root = await mkdtemp(join(tmpdir(), 'skills-'));
|
|
71
|
+
clones = new Set([DEFAULT_BRANCH]);
|
|
61
72
|
const skills = join(root, wsId, KB_DIR, 'Plugins');
|
|
62
73
|
await mkdir(join(skills, 'rfi', 'scripts'), { recursive: true });
|
|
63
74
|
await writeFile(join(skills, 'rfi', 'SKILL.md'), RFI_SKILL);
|
|
@@ -480,4 +491,217 @@ describe('SkillService', () => {
|
|
|
480
491
|
});
|
|
481
492
|
});
|
|
482
493
|
});
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Reading a DRAFT branch: what an agent needs to try the skill it has just
|
|
497
|
+
* written, with the answer saying plainly that nobody approved it.
|
|
498
|
+
*
|
|
499
|
+
* The draft starts as a copy of the released tree — which is what a clone of
|
|
500
|
+
* a branch with no skill changes on it is — and each test changes it.
|
|
501
|
+
*/
|
|
502
|
+
describe('reading a branch', () => {
|
|
503
|
+
const DRAFT = 'juan/skill-deck';
|
|
504
|
+
const draftWsId = workspaceIdForBranch(DRAFT);
|
|
505
|
+
const user = 'user@x.eu';
|
|
506
|
+
const draftKb = () => join(root, draftWsId, KB_DIR);
|
|
507
|
+
const named = async (service = svc()) =>
|
|
508
|
+
Object.fromEntries((await service.listSkills(user, { branch: DRAFT })).map((s) => [s.name, s]));
|
|
509
|
+
|
|
510
|
+
const writeDeck = (body = '# Deck\n') =>
|
|
511
|
+
mkdir(join(draftKb(), 'Plugins', 'make-deck'), { recursive: true }).then(() =>
|
|
512
|
+
writeFile(join(draftKb(), 'Plugins', 'make-deck', 'SKILL.md'), `---\ndescription: Decks.\n---\n\n${body}`),
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
beforeEach(async () => {
|
|
516
|
+
await cp(join(root, wsId), join(root, draftWsId), { recursive: true });
|
|
517
|
+
clones.add(DRAFT);
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
test('a skill that exists only on the branch is listed as unmerged; the released ones are not marked', async () => {
|
|
521
|
+
await writeDeck();
|
|
522
|
+
const list = await named();
|
|
523
|
+
expect(Object.keys(list).sort()).toEqual(['coding-guidelines', 'make-deck', 'rfi']);
|
|
524
|
+
expect(list['make-deck']).toMatchObject({ unmerged: true, branch: DRAFT, description: 'Decks.' });
|
|
525
|
+
// The skills the branch shares with the default branch are the released
|
|
526
|
+
// ones, read through a draft's clone: nothing about them is unapproved.
|
|
527
|
+
expect(list['rfi'].unmerged).toBeUndefined();
|
|
528
|
+
expect(list['rfi'].branch).toBeUndefined();
|
|
529
|
+
expect(list['coding-guidelines'].unmerged).toBeUndefined();
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
test('without a branch the draft-only skill is absent, and nothing carries a mark', async () => {
|
|
533
|
+
await writeDeck();
|
|
534
|
+
const list = await svc().listSkills(user);
|
|
535
|
+
expect(list.map((s) => s.name)).toEqual(['coding-guidelines', 'rfi']);
|
|
536
|
+
expect(list.some((s) => s.unmerged !== undefined || s.branch !== undefined)).toBe(false);
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
test('get_skill loads the draft-only skill, body starting with the unmerged line', async () => {
|
|
540
|
+
await writeDeck();
|
|
541
|
+
const res = await svc().getSkill(user, 'make-deck', undefined, { branch: DRAFT });
|
|
542
|
+
expect(res.ok && res.kind === 'skill').toBe(true);
|
|
543
|
+
if (!res.ok || res.kind !== 'skill') return;
|
|
544
|
+
expect(res.skill.body.split('\n')[0]).toBe(
|
|
545
|
+
`This skill is read from the unmerged branch "${DRAFT}" and is not approved.`,
|
|
546
|
+
);
|
|
547
|
+
expect(res.skill.body.split('\n')[0]).toBe(unmergedSkillNotice(DRAFT));
|
|
548
|
+
expect(res.skill.body).toContain('# Deck');
|
|
549
|
+
expect(res.skill).toMatchObject({ unmerged: true, branch: DRAFT, path: 'Plugins/make-deck' });
|
|
550
|
+
// Without the branch there is no such skill at all.
|
|
551
|
+
expect(await svc().getSkill(user, 'make-deck')).toEqual({ ok: false, error: 'not_found' });
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
test('a skill changed on the branch loads changed there and released without the branch', async () => {
|
|
555
|
+
await writeFile(
|
|
556
|
+
join(draftKb(), 'Plugins', 'rfi', 'SKILL.md'),
|
|
557
|
+
RFI_SKILL.replace('You answer RFIs.', 'You answer RFIs, in three paragraphs.'),
|
|
558
|
+
);
|
|
559
|
+
const service = svc();
|
|
560
|
+
const draft = await service.getSkill(user, 'rfi', undefined, { branch: DRAFT });
|
|
561
|
+
expect(draft.ok && draft.kind === 'skill' && draft.skill.body).toContain('in three paragraphs');
|
|
562
|
+
expect(draft.ok && draft.kind === 'skill' && draft.skill.body.split('\n')[0]).toBe(unmergedSkillNotice(DRAFT));
|
|
563
|
+
expect(draft.ok && draft.kind === 'skill' && draft.skill.unmerged).toBe(true);
|
|
564
|
+
expect((await named(service))['rfi']).toMatchObject({ unmerged: true, branch: DRAFT });
|
|
565
|
+
|
|
566
|
+
const released = await service.getSkill(user, 'rfi');
|
|
567
|
+
expect(released.ok && released.kind === 'skill' && released.skill.body).not.toContain('three paragraphs');
|
|
568
|
+
expect(released.ok && released.kind === 'skill' && released.skill.body).not.toContain('unmerged');
|
|
569
|
+
expect(released.ok && released.kind === 'skill' && released.skill.unmerged).toBeUndefined();
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
test('a skill unchanged on the branch carries no mark and no line — it is the released answer', async () => {
|
|
573
|
+
const service = svc();
|
|
574
|
+
expect(await service.getSkill(user, 'rfi', undefined, { branch: DRAFT })).toEqual(
|
|
575
|
+
await service.getSkill(user, 'rfi'),
|
|
576
|
+
);
|
|
577
|
+
expect(await service.getSkill(user, 'rfi', 'scripts/build_xlsx.py', { branch: DRAFT })).toEqual(
|
|
578
|
+
await service.getSkill(user, 'rfi', 'scripts/build_xlsx.py'),
|
|
579
|
+
);
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
test('a bundled file changed on the branch makes the skill unmerged, and rides beside its content', async () => {
|
|
583
|
+
// The SKILL.md is byte-identical: the difference is in the folder.
|
|
584
|
+
await writeFile(join(draftKb(), 'Plugins', 'rfi', 'scripts', 'build_xlsx.py'), 'print("xlsx v2")\n');
|
|
585
|
+
expect((await named())['rfi']).toMatchObject({ unmerged: true, branch: DRAFT });
|
|
586
|
+
const file = await svc().getSkill(user, 'rfi', 'scripts/build_xlsx.py', { branch: DRAFT });
|
|
587
|
+
expect(file).toEqual({
|
|
588
|
+
ok: true,
|
|
589
|
+
kind: 'file',
|
|
590
|
+
file: {
|
|
591
|
+
name: 'rfi',
|
|
592
|
+
file: 'scripts/build_xlsx.py',
|
|
593
|
+
path: 'Plugins/rfi/scripts/build_xlsx.py',
|
|
594
|
+
// The notice never goes INSIDE a script.
|
|
595
|
+
content: 'print("xlsx v2")\n',
|
|
596
|
+
unmerged: true,
|
|
597
|
+
branch: DRAFT,
|
|
598
|
+
},
|
|
599
|
+
});
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
test('an asset added on the branch is a difference too', async () => {
|
|
603
|
+
await writeFile(join(draftKb(), 'Plugins', 'rfi', 'scripts', 'extra.py'), 'print("new")\n');
|
|
604
|
+
expect((await named())['rfi']).toMatchObject({ unmerged: true, branch: DRAFT });
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
test('a skill deleted on the branch is absent there and still released without it', async () => {
|
|
608
|
+
await rm(join(draftKb(), 'Plugins', 'rfi'), { recursive: true });
|
|
609
|
+
const service = svc();
|
|
610
|
+
expect(Object.keys(await named(service))).toEqual(['coding-guidelines']);
|
|
611
|
+
expect(await service.getSkill(user, 'rfi', undefined, { branch: DRAFT })).toEqual({
|
|
612
|
+
ok: false,
|
|
613
|
+
error: 'not_found',
|
|
614
|
+
});
|
|
615
|
+
expect((await service.listSkills(user)).map((s) => s.name)).toContain('rfi');
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
test("access is judged on the branch, with that branch's rules", async () => {
|
|
619
|
+
// The same caller, the same skill: readable on the default branch,
|
|
620
|
+
// denied in the draft's own workspace.
|
|
621
|
+
const perBranch: IAccessControl = {
|
|
622
|
+
canRead: async () => true,
|
|
623
|
+
canReadBatch: async (w: string, _e: string, paths: string[]) =>
|
|
624
|
+
new Map(paths.map((p) => [p, !(w === draftWsId && p.includes('/rfi/'))])),
|
|
625
|
+
} as unknown as IAccessControl;
|
|
626
|
+
const service = svc(perBranch);
|
|
627
|
+
expect(Object.keys(await named(service))).toEqual(['coding-guidelines']);
|
|
628
|
+
// Not `forbidden`: a draft skill the caller may not read there is one
|
|
629
|
+
// they must not learn exists.
|
|
630
|
+
expect(await service.getSkill(user, 'rfi', undefined, { branch: DRAFT })).toEqual({
|
|
631
|
+
ok: false,
|
|
632
|
+
error: 'not_found',
|
|
633
|
+
});
|
|
634
|
+
expect(await service.getSkill(user, 'rfi', 'scripts/build_xlsx.py', { branch: DRAFT })).toEqual({
|
|
635
|
+
ok: false,
|
|
636
|
+
error: 'not_found',
|
|
637
|
+
});
|
|
638
|
+
// The released answer is untouched by the draft's rules.
|
|
639
|
+
expect((await service.listSkills(user)).map((s) => s.name)).toContain('rfi');
|
|
640
|
+
expect((await service.getSkill(user, 'rfi')).ok).toBe(true);
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
test('a branch that does not exist is a 404 naming it, as on the file tools', async () => {
|
|
644
|
+
const service = svc();
|
|
645
|
+
await expect(service.listSkills(user, { branch: 'juan/typo' })).rejects.toMatchObject({
|
|
646
|
+
status: 404,
|
|
647
|
+
kind: 'branch-not-found',
|
|
648
|
+
branch: 'juan/typo',
|
|
649
|
+
message: 'There is no branch named juan/typo.',
|
|
650
|
+
});
|
|
651
|
+
await expect(service.getSkill(user, 'rfi', undefined, { branch: 'juan/typo' })).rejects.toMatchObject({
|
|
652
|
+
status: 404,
|
|
653
|
+
kind: 'branch-not-found',
|
|
654
|
+
});
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
test('naming the default branch answers exactly as naming nothing', async () => {
|
|
658
|
+
await writeDeck();
|
|
659
|
+
const service = svc();
|
|
660
|
+
expect(await service.listSkills(user, { branch: DEFAULT_BRANCH })).toEqual(await service.listSkills(user));
|
|
661
|
+
expect(await service.getSkill(user, 'rfi', undefined, { branch: DEFAULT_BRANCH })).toEqual(
|
|
662
|
+
await service.getSkill(user, 'rfi'),
|
|
663
|
+
);
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
test('a branch read is never cached: the next call sees what the agent just wrote', async () => {
|
|
667
|
+
const service = svc();
|
|
668
|
+
expect(Object.keys(await named(service))).not.toContain('make-deck');
|
|
669
|
+
await writeDeck();
|
|
670
|
+
// Same instance, no `invalidate()`: the released catalog is cached, a
|
|
671
|
+
// draft is not.
|
|
672
|
+
expect(Object.keys(await named(service))).toContain('make-deck');
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
// The `unmerged` verdict compares two folder digests, and the released
|
|
676
|
+
// side of that comparison is memoized per cached catalog (it used to be
|
|
677
|
+
// re-hashed once per skill per call). These two pin what the memo may and
|
|
678
|
+
// may not do.
|
|
679
|
+
test('the memo never makes a draft stale: an edit on the branch is seen on the next call', async () => {
|
|
680
|
+
const service = svc();
|
|
681
|
+
expect((await named(service))['rfi'].unmerged).toBeUndefined();
|
|
682
|
+
await writeFile(join(draftKb(), 'Plugins', 'rfi', 'scripts', 'build_xlsx.py'), 'print("on the branch")\n');
|
|
683
|
+
// Same instance, no `invalidate()`: the draft's digests are built per
|
|
684
|
+
// call along with its catalog, so the agent's own last write shows.
|
|
685
|
+
expect((await named(service))['rfi']).toMatchObject({ unmerged: true, branch: DRAFT });
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
test('the released digests live and die with the released catalog', async () => {
|
|
689
|
+
const service = svc();
|
|
690
|
+
expect((await named(service))['rfi'].unmerged).toBeUndefined();
|
|
691
|
+
// An asset of the RELEASED copy changes. The catalog caches the file
|
|
692
|
+
// list, not the contents, so this is visible to a digest the moment one
|
|
693
|
+
// is taken — and the memo holds the pre-change one for the same TTL the
|
|
694
|
+
// rest of the released answer is held for.
|
|
695
|
+
await writeFile(join(root, wsId, KB_DIR, 'Plugins', 'rfi', 'scripts', 'build_xlsx.py'), 'print("released")\n');
|
|
696
|
+
expect((await named(service))['rfi'].unmerged).toBeUndefined();
|
|
697
|
+
service.invalidate();
|
|
698
|
+
expect((await named(service))['rfi']).toMatchObject({ unmerged: true, branch: DRAFT });
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
test('the global set (no caller) can be read from a branch too', async () => {
|
|
702
|
+
await writeDeck();
|
|
703
|
+
const list = await svc().listSkills(undefined, { branch: DRAFT });
|
|
704
|
+
expect(list.find((s) => s.name === 'make-deck')).toMatchObject({ unmerged: true, branch: DRAFT });
|
|
705
|
+
});
|
|
706
|
+
});
|
|
483
707
|
});
|
|
@@ -3,16 +3,12 @@ import { logger } from '../../shared/logging.js';
|
|
|
3
3
|
|
|
4
4
|
const log = logger('skills');
|
|
5
5
|
import '../auth/auth.middleware.js'; // Express Request.userId / userEmail augmentation
|
|
6
|
-
import {
|
|
7
|
-
joinBranchFor,
|
|
8
|
-
type AuthUser,
|
|
9
|
-
type IWorkflowService,
|
|
10
|
-
} from '@bevel-software/platform-shared';
|
|
6
|
+
import type { AuthUser, IWorkflowService } from '@bevel-software/platform-shared';
|
|
11
7
|
import type { IAccessControl } from '../access/access-control.interface.js';
|
|
12
|
-
import {
|
|
8
|
+
import { folderTarget } from '../access/access-requests.contract.js';
|
|
9
|
+
import { AccessRequestsService } from '../access/access-requests.service.js';
|
|
13
10
|
import { WorkflowDomainError } from '../../shared/domain-errors.js';
|
|
14
11
|
import { domainErrorBody } from '../../shared/http-errors.js';
|
|
15
|
-
import { isAbsence } from '../../shared/fs.contract.js';
|
|
16
12
|
import type { KbContext } from '../../shared/kb-context.js';
|
|
17
13
|
import type { WorkspaceService } from '../workspace/workspace.service.js';
|
|
18
14
|
import type { JoinRequestsService } from '../plugins/join-requests.service.js';
|
|
@@ -28,13 +24,11 @@ import type { ISkillService } from './skills.contract.js';
|
|
|
28
24
|
*
|
|
29
25
|
* Why it exists: linking a skill into a plugin needs write on the skill's
|
|
30
26
|
* rules (the link grants the plugin's principal there), and a plugin manager
|
|
31
|
-
* often has no say over a skill some other scope owns. The request is
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* path rather than a plugin name — the branch-name convention takes any
|
|
37
|
-
* exact string).
|
|
27
|
+
* often has no say over a skill some other scope owns. The request is opened
|
|
28
|
+
* by the shared `AccessRequestsService` — the same one the Manage access
|
|
29
|
+
* dialog's Request access uses — keyed on the skill FOLDER path, so this
|
|
30
|
+
* button and a Can edit request on that same folder are ONE request, shown in
|
|
31
|
+
* the skill page's banner and in the folder's dialog alike.
|
|
38
32
|
*
|
|
39
33
|
* Fail-closed like the skill catalog: a skill the caller cannot read answers
|
|
40
34
|
* as unknown, and the editors' listing answers `[]` to anyone else.
|
|
@@ -49,10 +43,18 @@ export function createSkillAccessRequestRoutes(deps: {
|
|
|
49
43
|
resolveUser: (req: express.Request) => Promise<AuthUser | null>;
|
|
50
44
|
}): express.Router {
|
|
51
45
|
const { skillService, accessControl, workflow, workspaceService, joinRequests, kb, resolveUser } = deps;
|
|
52
|
-
const { kbDirName } = kb;
|
|
53
46
|
const router = express.Router();
|
|
54
47
|
const wsId = () => kb.defaultWorkspaceId();
|
|
55
48
|
const rulesOf = (folder: string) => `${folder}/access.md`;
|
|
49
|
+
// The same opener the Manage access dialog uses, pointed at a skill's
|
|
50
|
+
// folder. Keyed on that folder's PATH, which is what makes this button and
|
|
51
|
+
// a Can edit request on the same folder one request rather than two.
|
|
52
|
+
const requests = new AccessRequestsService({
|
|
53
|
+
workflow,
|
|
54
|
+
workspaceService,
|
|
55
|
+
lifecycle: joinRequests,
|
|
56
|
+
kb,
|
|
57
|
+
});
|
|
56
58
|
|
|
57
59
|
/** The skill the caller may read, by name — or null after answering 404. */
|
|
58
60
|
async function readableSkill(
|
|
@@ -82,65 +84,13 @@ export function createSkillAccessRequestRoutes(deps: {
|
|
|
82
84
|
res.status(409).json({ error: 'You can already edit this skill', kind: 'already-writable' });
|
|
83
85
|
return;
|
|
84
86
|
}
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
(cr) => cr.state === 'open' && cr.branch === branch,
|
|
91
|
-
);
|
|
92
|
-
if (existing) {
|
|
93
|
-
res.json({ ok: true, number: existing.number });
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
// The branch may already exist (a request listed as closed, a retry
|
|
97
|
-
// after a failed open). Existence is PROBED, before and — if creation
|
|
98
|
-
// fails — after: the race shows up as "already exists" locally or as a
|
|
99
|
-
// rejected push when origin got there first, and a message is not a
|
|
100
|
-
// contract. A branch that is there is proceeded against; anything else
|
|
101
|
-
// is a real failure, and a proposal on a branch that was not made would
|
|
102
|
-
// be worse than the error. STRICT: the list proves absence, and a
|
|
103
|
-
// listing that could not fetch proves nothing — it throws instead of
|
|
104
|
-
// serving refs from before the branch was made (or deleted).
|
|
105
|
-
const branchExists = async () =>
|
|
106
|
-
(await workflow.listBranches(wsId(), { freshFetch: true, strictFetch: true })).some(
|
|
107
|
-
(b) => b.name === branch,
|
|
108
|
-
);
|
|
109
|
-
if (!(await branchExists())) {
|
|
110
|
-
try {
|
|
111
|
-
await workflow.createBranch(wsId(), branch, kb.defaultBranch);
|
|
112
|
-
} catch (err) {
|
|
113
|
-
if (!(await branchExists())) throw err;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
const ws = await workspaceService.getOrCreateForBranch(branch);
|
|
117
|
-
const accessPath = `${kbDirName}/${rulesOf(folder)}`;
|
|
118
|
-
// Only a PROVEN absence reads as "no rules yet": any other failure must
|
|
119
|
-
// not be turned into an empty file that a splice then overwrites.
|
|
120
|
-
const current = await workspaceService.readFile(ws.id, accessPath).catch((err: unknown) => {
|
|
121
|
-
if (!isAbsence(err)) throw err;
|
|
122
|
-
return '';
|
|
123
|
-
});
|
|
124
|
-
const spliced = spliceGrant(
|
|
125
|
-
current,
|
|
126
|
-
'write',
|
|
127
|
-
{ kind: 'user', email: user.email, displayName: user.name },
|
|
128
|
-
{ target: 'folder' },
|
|
129
|
-
);
|
|
130
|
-
if (spliced.changed) {
|
|
131
|
-
await workspaceService.writeFile(ws.id, accessPath, spliced.text);
|
|
132
|
-
await workflow.commitChanges(ws.id, user, `Request write access to ${name}`);
|
|
133
|
-
}
|
|
134
|
-
const detail = await workflow.openChangeRequest(ws.id, user, {
|
|
135
|
-
sourceBranch: branch,
|
|
136
|
-
targetBranch: kb.defaultBranch,
|
|
137
|
-
title: `Access request: ${name}`,
|
|
138
|
-
description:
|
|
139
|
-
`${user.name} asked to edit the skill ${name}. An editor of the skill accepts by ` +
|
|
140
|
-
`granting the access this branch proposes; the request closes itself once ` +
|
|
141
|
-
`every proposal has landed.`,
|
|
87
|
+
const { number } = await requests.open({
|
|
88
|
+
user,
|
|
89
|
+
target: folderTarget(folder),
|
|
90
|
+
itemName: name,
|
|
91
|
+
level: 'write',
|
|
142
92
|
});
|
|
143
|
-
res.json({ ok: true, number
|
|
93
|
+
res.json({ ok: true, number });
|
|
144
94
|
} catch (err) {
|
|
145
95
|
if (err instanceof WorkflowDomainError) {
|
|
146
96
|
res.status(err.status).json(domainErrorBody(err));
|
|
@@ -171,7 +121,7 @@ export function createSkillAccessRequestRoutes(deps: {
|
|
|
171
121
|
const ctx = await requireEditor(req, res, () => res.json({ requests: [] }));
|
|
172
122
|
if (!ctx) return;
|
|
173
123
|
const crs = await workflow.listChangeRequests();
|
|
174
|
-
res.json({ requests: await joinRequests.list(ctx.folder, ctx.folder, crs, ctx.user) });
|
|
124
|
+
res.json({ requests: await joinRequests.list(ctx.folder, folderTarget(ctx.folder), crs, ctx.user) });
|
|
175
125
|
} catch (err) {
|
|
176
126
|
log.error('failed to list access requests:', { err });
|
|
177
127
|
res.status(500).json({ error: 'Failed to list access requests' });
|
|
@@ -192,7 +142,7 @@ export function createSkillAccessRequestRoutes(deps: {
|
|
|
192
142
|
res.status(404).json({ error: 'Not found' });
|
|
193
143
|
return;
|
|
194
144
|
}
|
|
195
|
-
res.json({ closed: await joinRequests.reconcile(ctx.folder, ctx.folder, cr, ctx.user) });
|
|
145
|
+
res.json({ closed: await joinRequests.reconcile(ctx.folder, folderTarget(ctx.folder), cr, ctx.user) });
|
|
196
146
|
} catch (err) {
|
|
197
147
|
log.error('failed to reconcile an access request:', { err });
|
|
198
148
|
res.status(500).json({ error: 'Failed to reconcile the request' });
|