@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
|
@@ -84,11 +84,14 @@ export type HolderList = {
|
|
|
84
84
|
export type PathHolders = { read: HolderList; write: HolderList };
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
|
-
* One file's holders where it is now and where a move would put
|
|
88
|
-
* {@link IAccessControl.prospectiveHolders}.
|
|
87
|
+
* One file's or folder's holders where it is now and where a move would put
|
|
88
|
+
* it — see {@link IAccessControl.prospectiveHolders}.
|
|
89
89
|
*/
|
|
90
90
|
export type ProspectiveHolders = { before: PathHolders; after: PathHolders };
|
|
91
91
|
|
|
92
|
+
/** One caller's own verdict per access verb on one path. */
|
|
93
|
+
export type PathVerbs = { read: boolean; write: boolean; download: boolean; owner: boolean };
|
|
94
|
+
|
|
92
95
|
/**
|
|
93
96
|
* Per-verb sources of a principal's access on a target. Only verbs the principal
|
|
94
97
|
* actually holds (via a named file entry) appear; each maps to the closest-first
|
|
@@ -358,19 +361,17 @@ export interface IAccessControl {
|
|
|
358
361
|
}>;
|
|
359
362
|
|
|
360
363
|
/**
|
|
361
|
-
* Who can open and who can edit one file where it IS, and where a
|
|
362
|
-
* would put it. `toPath` names a path that does not exist yet — the
|
|
363
|
-
* of the call is to answer before the move happens — so the
|
|
364
|
-
*
|
|
365
|
-
*
|
|
364
|
+
* Who can open and who can edit one file or folder where it IS, and where a
|
|
365
|
+
* move would put it. `toPath` names a path that does not exist yet — the
|
|
366
|
+
* point of the call is to answer before the move happens — so the
|
|
367
|
+
* resolution is over what travels with the bytes: a file's OWN rules (its
|
|
368
|
+
* frontmatter, read from `fromPath`) layered over the destination's folder
|
|
369
|
+
* chain, or a folder's `access.md` files keyed at the directories they
|
|
370
|
+
* land in. Either way the rules the source inherited from its old parent
|
|
371
|
+
* are left behind and the new parent's apply.
|
|
366
372
|
*
|
|
367
373
|
* Writes nothing and moves nothing. The move confirmation diffs the two
|
|
368
374
|
* sides to name who loses and who gains access.
|
|
369
|
-
*
|
|
370
|
-
* A FILE question only: a `fromPath` that is a directory is refused with a
|
|
371
|
-
* 400. A folder's access is its own `access.md` — which moves with it and
|
|
372
|
-
* governs everything beneath it — so resolving it as a file would name the
|
|
373
|
-
* wrong principals with the same confidence as the right ones.
|
|
374
375
|
*/
|
|
375
376
|
prospectiveHolders(
|
|
376
377
|
workspaceId: string,
|
|
@@ -378,6 +379,36 @@ export interface IAccessControl {
|
|
|
378
379
|
toPath: string,
|
|
379
380
|
): Promise<ProspectiveHolders>;
|
|
380
381
|
|
|
382
|
+
/**
|
|
383
|
+
* The CALLER'S OWN `{ read, write, download, owner }` at `toPath` as they
|
|
384
|
+
* will be once `fromPath` has been moved (`sourceRemains: false`, the
|
|
385
|
+
* default) or copied (`sourceRemains: true`) there — the question a move's
|
|
386
|
+
* or copy's dry run asks, which the same four gates at `toPath` cannot
|
|
387
|
+
* answer because what travels with the bytes is not at the destination yet:
|
|
388
|
+
* the `access.md` files inside a folder, keyed at the directories they land
|
|
389
|
+
* in, and a file's own frontmatter.
|
|
390
|
+
*
|
|
391
|
+
* What a single file's rules GOVERN follows the name it lands under, not the
|
|
392
|
+
* one it leaves: landing as a directory's `access.md` they are that
|
|
393
|
+
* directory's rules (so copying `Sales/access.md` to `Legal/access.md`
|
|
394
|
+
* previews the access `Legal/` is about to have), and landing under any
|
|
395
|
+
* other name they are the file's own and govern nothing but it. Either way
|
|
396
|
+
* the answer is the one the gates will give a moment later.
|
|
397
|
+
*
|
|
398
|
+
* PREVIEW ONLY. It describes a tree that does not exist, so it must never
|
|
399
|
+
* decide whether an operation may run: the gates keep resolving against the
|
|
400
|
+
* tree that does. It writes nothing and moves nothing, and it answers the
|
|
401
|
+
* caller's own verbs only — never the content of an `access.md` they may
|
|
402
|
+
* not read.
|
|
403
|
+
*/
|
|
404
|
+
previewAccessAfterRelocation(
|
|
405
|
+
workspaceId: string,
|
|
406
|
+
userEmail: string,
|
|
407
|
+
fromPath: string,
|
|
408
|
+
toPath: string,
|
|
409
|
+
opts?: { sourceRemains?: boolean },
|
|
410
|
+
): Promise<PathVerbs>;
|
|
411
|
+
|
|
381
412
|
/**
|
|
382
413
|
* Finite expanded email set for configured users who could approve this path
|
|
383
414
|
* — role members + direct user grants, minus anyone denied. The built-in
|
|
@@ -5,7 +5,7 @@ import { logger } from '../../shared/logging.js';
|
|
|
5
5
|
const log = logger('access');
|
|
6
6
|
|
|
7
7
|
import { isAbsence, type ITreeWalker, type WalkListener } from '../../shared/fs.contract.js';
|
|
8
|
-
import { isGitTimeout, type IGitRunner } from '../../shared/git.contract.js';
|
|
8
|
+
import { assertOneSpecPerLine, isGitTimeout, type IGitRunner } from '../../shared/git.contract.js';
|
|
9
9
|
import { hashEmail } from '../../shared/email-identity.js';
|
|
10
10
|
import { NodeGitRunner } from '../workflow/git/node-git-runner.js';
|
|
11
11
|
import type { WorkspaceService } from '../workspace/workspace.service.js';
|
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
GrantSource,
|
|
20
20
|
GrantSources,
|
|
21
21
|
PathHolders,
|
|
22
|
+
PathVerbs,
|
|
22
23
|
ProspectiveHolders,
|
|
23
24
|
ResolvedPrincipal,
|
|
24
25
|
} from './access-control.interface.js';
|
|
@@ -31,7 +32,6 @@ import {
|
|
|
31
32
|
} from '@bevel-software/platform-shared';
|
|
32
33
|
import type { KbContext } from '../../shared/kb-context.js';
|
|
33
34
|
import { AccessConfigError, AccessUnreadableError } from '../access-model/access-errors.js';
|
|
34
|
-
import { WorkflowDomainError } from '../../shared/domain-errors.js';
|
|
35
35
|
import { synthesizePluginPrincipals } from '../access-model/plugin-principals.js';
|
|
36
36
|
import {
|
|
37
37
|
GROUPS_YAML,
|
|
@@ -81,6 +81,12 @@ async function catFileBatch(
|
|
|
81
81
|
): Promise<(string | null)[]> {
|
|
82
82
|
// Bytes, not text: every `<size>` in the header counts bytes, and the walk
|
|
83
83
|
// below has to step by that count through content that is not all ASCII.
|
|
84
|
+
// One spec per LINE is the protocol, and this walk depends on getting back
|
|
85
|
+
// exactly as many answers as it sent: a spec carrying a line break would
|
|
86
|
+
// insert an extra answer and shift every result after it, so each directory
|
|
87
|
+
// would be handed the NEXT one's `access.md`. Paths are refused a control
|
|
88
|
+
// character at the normaliser; this is the fence at the protocol.
|
|
89
|
+
assertOneSpecPerLine(specs);
|
|
84
90
|
const { stdout: out } = await runner.run(repoDir, ['cat-file', '--batch'], {
|
|
85
91
|
input: `${specs.join('\n')}\n`,
|
|
86
92
|
encoding: 'buffer',
|
|
@@ -951,6 +957,95 @@ function resolveDenialSourcesForVerb(
|
|
|
951
957
|
return out;
|
|
952
958
|
}
|
|
953
959
|
|
|
960
|
+
/**
|
|
961
|
+
* The model as it will be once `fromPath` has been moved or copied to
|
|
962
|
+
* `toPath`: every `access.md` the operation carries, keyed at the directory
|
|
963
|
+
* it lands in, since the rules a folder carries travel with its bytes.
|
|
964
|
+
*
|
|
965
|
+
* What travels is BYTES, and the name they land under decides what they
|
|
966
|
+
* govern — `landing` is the caller's answer to that (see
|
|
967
|
+
* `relocationSubject`), already parsed as the destination path:
|
|
968
|
+
*
|
|
969
|
+
* - a FOLDER takes every `access.md` at or under it, each re-keyed to the
|
|
970
|
+
* directory it lands in, and `landing` plays no part;
|
|
971
|
+
* - a FILE governs a directory exactly when it lands as that directory's
|
|
972
|
+
* `access.md`, whatever it was called before — a copy `copy_file` allows,
|
|
973
|
+
* and one that governs the destination folder the moment it lands. Its key
|
|
974
|
+
* is a PARENT directory, never a path under `fromPath`, which is why the
|
|
975
|
+
* prefix walk above cannot see it.
|
|
976
|
+
*
|
|
977
|
+
* The emptied side is the mirror: an `access.md` that MOVES stops governing
|
|
978
|
+
* the directory it left, whether or not it governs anything where it lands.
|
|
979
|
+
*
|
|
980
|
+
* PREVIEW ONLY. Nothing here reads disk and nothing here decides whether an
|
|
981
|
+
* operation may run — the gates resolve against the model as it IS. This
|
|
982
|
+
* answers the other question, the one a dry run asks: what will be true
|
|
983
|
+
* afterwards. Judging the destination as it stands today is what made a
|
|
984
|
+
* rename warn about losing the owner access the folder's own `access.md` was
|
|
985
|
+
* about to hand straight back.
|
|
986
|
+
*
|
|
987
|
+
* `sourceRemains` is the difference between a copy and a move: a copy leaves
|
|
988
|
+
* the rules where they were, a move takes them away. It changes the answer
|
|
989
|
+
* only when the destination resolves through a directory the source
|
|
990
|
+
* occupied; nothing else consults those keys.
|
|
991
|
+
*/
|
|
992
|
+
function withRelocatedAccessFiles(
|
|
993
|
+
model: AccessModel,
|
|
994
|
+
fromPath: string,
|
|
995
|
+
toPath: string,
|
|
996
|
+
opts: { sourceRemains: boolean; sourceIsFolder: boolean; landing?: AccessFile | null },
|
|
997
|
+
): AccessModel {
|
|
998
|
+
/** Directories the operation takes an `access.md` AWAY from. */
|
|
999
|
+
const emptied: string[] = [];
|
|
1000
|
+
/** Directories it leaves one IN, with the file as it will be read there. */
|
|
1001
|
+
const landing: { toDir: string; file: AccessFile }[] = [];
|
|
1002
|
+
if (opts.sourceIsFolder) {
|
|
1003
|
+
for (const [dir, file] of model.accessFilesByDir) {
|
|
1004
|
+
if (dir !== fromPath && !dir.startsWith(`${fromPath}/`)) continue;
|
|
1005
|
+
emptied.push(dir);
|
|
1006
|
+
landing.push({ toDir: `${toPath}${dir.slice(fromPath.length)}`, file });
|
|
1007
|
+
}
|
|
1008
|
+
} else {
|
|
1009
|
+
const fromDir = parentDirOf(fromPath);
|
|
1010
|
+
if (isAccessMdPath(fromPath) && model.accessFilesByDir.has(fromDir)) emptied.push(fromDir);
|
|
1011
|
+
if (opts.landing) landing.push({ toDir: parentDirOf(toPath), file: opts.landing });
|
|
1012
|
+
}
|
|
1013
|
+
if (emptied.length === 0 && landing.length === 0) return model;
|
|
1014
|
+
const accessFilesByDir = new Map(model.accessFilesByDir);
|
|
1015
|
+
if (!opts.sourceRemains) for (const dir of emptied) accessFilesByDir.delete(dir);
|
|
1016
|
+
for (const { toDir, file } of landing) {
|
|
1017
|
+
// A directory that already has an `access.md` keeps it. The operation
|
|
1018
|
+
// would be refused for landing on something that exists long before the
|
|
1019
|
+
// two files could meet, so the model must not invent a merge either.
|
|
1020
|
+
if (accessFilesByDir.has(toDir)) continue;
|
|
1021
|
+
accessFilesByDir.set(toDir, {
|
|
1022
|
+
...file,
|
|
1023
|
+
dir: toDir,
|
|
1024
|
+
path: toDir ? `${toDir}/access.md` : 'access.md',
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
return { ...model, accessFilesByDir };
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
/** The directory a repo-relative file path sits in; `''` for the repo root. */
|
|
1031
|
+
function parentDirOf(relativePath: string): string {
|
|
1032
|
+
const cut = relativePath.lastIndexOf('/');
|
|
1033
|
+
return cut === -1 ? '' : relativePath.slice(0, cut);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
/**
|
|
1037
|
+
* One file's own-entries, parsed as the path `asName` would be read.
|
|
1038
|
+
*
|
|
1039
|
+
* The same frontmatter means two different things by name: in an `access.md`
|
|
1040
|
+
* it is the FOLDER's rules (and the file governs itself only through the
|
|
1041
|
+
* body format — see {@link accessMdSelfEntries}), and anywhere else it is the
|
|
1042
|
+
* file's own. Which is why a preview must read the bytes as the name they
|
|
1043
|
+
* LAND under, not the one they are leaving.
|
|
1044
|
+
*/
|
|
1045
|
+
function ownEntriesFromText(text: string, asName: string): OwnEntries | null {
|
|
1046
|
+
return isAccessMdPath(asName) ? accessMdSelfEntries(text) : parseOwnAccessEntries(text);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
954
1049
|
/**
|
|
955
1050
|
* Build the repo-root → path chain of directory scopes that govern
|
|
956
1051
|
* `relativePath`, e.g. `Knowledge/Sales/Foo.md` →
|
|
@@ -1586,16 +1681,25 @@ export class AccessControlService implements IAccessControl {
|
|
|
1586
1681
|
}
|
|
1587
1682
|
|
|
1588
1683
|
/**
|
|
1589
|
-
* The read and write holders of one file at its current path and
|
|
1590
|
-
* it has not moved to yet (see the interface).
|
|
1684
|
+
* The read and write holders of one file or folder at its current path and
|
|
1685
|
+
* at a path it has not moved to yet (see the interface).
|
|
1686
|
+
*
|
|
1687
|
+
* A move changes nothing about what the thing itself carries — a file's
|
|
1688
|
+
* frontmatter and a folder's `access.md` files travel with the bytes — and
|
|
1689
|
+
* everything about the folder chain above it. So the hypothetical side is
|
|
1690
|
+
* the ordinary resolution at the destination path, over what travels:
|
|
1591
1691
|
*
|
|
1592
|
-
*
|
|
1593
|
-
*
|
|
1594
|
-
*
|
|
1595
|
-
*
|
|
1596
|
-
*
|
|
1597
|
-
*
|
|
1598
|
-
*
|
|
1692
|
+
* - a FILE's own-entries, read from the SOURCE (nothing sits at the
|
|
1693
|
+
* destination to read) and parsed as the name they LAND under, layered
|
|
1694
|
+
* over the destination's ancestors — the two sides can differ, because
|
|
1695
|
+
* the same frontmatter is a folder's rules in an `access.md` and the
|
|
1696
|
+
* file's own anywhere else;
|
|
1697
|
+
* - a FOLDER's `access.md` files, keyed at the directories they land in,
|
|
1698
|
+
* and a file landing as a directory's `access.md` at the directory it
|
|
1699
|
+
* lands in (`withRelocatedAccessFiles`). A folder has no frontmatter of
|
|
1700
|
+
* its own, so it passes none.
|
|
1701
|
+
*
|
|
1702
|
+
* One model load and one file read serve all four lookups.
|
|
1599
1703
|
*/
|
|
1600
1704
|
async prospectiveHolders(
|
|
1601
1705
|
workspaceId: string,
|
|
@@ -1604,31 +1708,64 @@ export class AccessControlService implements IAccessControl {
|
|
|
1604
1708
|
): Promise<ProspectiveHolders> {
|
|
1605
1709
|
const model = await this.loadModel(workspaceId);
|
|
1606
1710
|
const repoDir = await this.repoDir(workspaceId);
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
// Only absence is an answer here (see `isAbsence`): a probe that failed on
|
|
1614
|
-
// permissions or I/O does not say "this is a file", and folding it into
|
|
1615
|
-
// one would let the very case above through on an unreadable source.
|
|
1616
|
-
const fromStat = await fs.stat(path.join(repoDir, fromPath)).catch((err: unknown) => {
|
|
1617
|
-
if (isAbsence(err)) return null;
|
|
1618
|
-
throw err;
|
|
1711
|
+
const { isFolder, ownHere, ownThere, landing } =
|
|
1712
|
+
await this.relocationSubject(repoDir, fromPath, toPath);
|
|
1713
|
+
const after = withRelocatedAccessFiles(model, fromPath, toPath, {
|
|
1714
|
+
sourceRemains: false,
|
|
1715
|
+
sourceIsFolder: isFolder,
|
|
1716
|
+
landing,
|
|
1619
1717
|
});
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
400,
|
|
1624
|
-
);
|
|
1625
|
-
}
|
|
1626
|
-
const own = await this.readOwnEntries(repoDir, fromPath);
|
|
1627
|
-
const holdersAt = (relativePath: string): PathHolders => ({
|
|
1628
|
-
read: eligibleHoldersResolved(model, 'read', relativePath, own),
|
|
1629
|
-
write: eligibleHoldersResolved(model, 'write', relativePath, own),
|
|
1718
|
+
const holdersAt = (m: AccessModel, relativePath: string, own: OwnEntries | null): PathHolders => ({
|
|
1719
|
+
read: eligibleHoldersResolved(m, 'read', relativePath, own),
|
|
1720
|
+
write: eligibleHoldersResolved(m, 'write', relativePath, own),
|
|
1630
1721
|
});
|
|
1631
|
-
return {
|
|
1722
|
+
return {
|
|
1723
|
+
before: holdersAt(model, fromPath, ownHere),
|
|
1724
|
+
after: holdersAt(after, toPath, ownThere),
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
/**
|
|
1729
|
+
* The caller's own verbs at `toPath` as they will be once `fromPath` has
|
|
1730
|
+
* been moved or copied there (see the interface).
|
|
1731
|
+
*
|
|
1732
|
+
* Identical to asking `canRead`/`canWrite`/`canDownload`/`canOwner` at
|
|
1733
|
+
* `toPath` whenever nothing travels — an ordinary file with no rules of its
|
|
1734
|
+
* own, or a folder with no `access.md` of its own — so the cases this was
|
|
1735
|
+
* not written for answer exactly as they always did.
|
|
1736
|
+
*
|
|
1737
|
+
* What travels is what `prospectiveHolders` says travels, and for the same
|
|
1738
|
+
* reason: the preview and the dialog's holder lists must not disagree about
|
|
1739
|
+
* one move. A file's own-entries come from the SOURCE, where the bytes
|
|
1740
|
+
* carrying them still are — asking the destination, which does not exist
|
|
1741
|
+
* yet, always answered null and so dropped the frontmatter a rename hands
|
|
1742
|
+
* straight back (requirement 6: what the caller has afterwards equals what
|
|
1743
|
+
* the preview said) — and they are parsed as the name they LAND under,
|
|
1744
|
+
* because that is the parse the gates will use a moment later.
|
|
1745
|
+
*/
|
|
1746
|
+
async previewAccessAfterRelocation(
|
|
1747
|
+
workspaceId: string,
|
|
1748
|
+
userEmail: string,
|
|
1749
|
+
fromPath: string,
|
|
1750
|
+
toPath: string,
|
|
1751
|
+
opts?: { sourceRemains?: boolean },
|
|
1752
|
+
): Promise<PathVerbs> {
|
|
1753
|
+
const repoDir = await this.repoDir(workspaceId);
|
|
1754
|
+
const { isFolder, ownThere: own, landing } =
|
|
1755
|
+
await this.relocationSubject(repoDir, fromPath, toPath);
|
|
1756
|
+
const model = withRelocatedAccessFiles(
|
|
1757
|
+
await this.loadModel(workspaceId),
|
|
1758
|
+
fromPath,
|
|
1759
|
+
toPath,
|
|
1760
|
+
{ sourceRemains: opts?.sourceRemains === true, sourceIsFolder: isFolder, landing },
|
|
1761
|
+
);
|
|
1762
|
+
const machineOwned = this.machineOwnedWriteRule(userEmail, toPath);
|
|
1763
|
+
return {
|
|
1764
|
+
read: canReadResolved(model, userEmail, toPath, own),
|
|
1765
|
+
write: machineOwned ?? hasPermissionResolved(model, 'write', userEmail, toPath, own),
|
|
1766
|
+
download: hasPermissionResolved(model, 'download', userEmail, toPath, own),
|
|
1767
|
+
owner: hasPermissionResolved(model, 'owner', userEmail, toPath, own),
|
|
1768
|
+
};
|
|
1632
1769
|
}
|
|
1633
1770
|
|
|
1634
1771
|
async eligibleWriterEmails(
|
|
@@ -2219,8 +2356,69 @@ export class AccessControlService implements IAccessControl {
|
|
|
2219
2356
|
} catch {
|
|
2220
2357
|
return null;
|
|
2221
2358
|
}
|
|
2222
|
-
|
|
2223
|
-
|
|
2359
|
+
return ownEntriesFromText(text, relativePath);
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
/**
|
|
2363
|
+
* What a move or a copy of `fromPath` to `toPath` CARRIES — the one disk
|
|
2364
|
+
* read both preview surfaces need:
|
|
2365
|
+
*
|
|
2366
|
+
* - `isFolder`: the source is a folder, so the `access.md` files under it
|
|
2367
|
+
* travel, keyed at the directories they land in;
|
|
2368
|
+
* - `ownHere` / `ownThere`: a file's own-entries as the SOURCE's name reads
|
|
2369
|
+
* them and as the DESTINATION's name will. They differ because the same
|
|
2370
|
+
* frontmatter means two things by name (see `ownEntriesFromText`), so
|
|
2371
|
+
* `Sales/access.md` copied to `Legal/rules-copy.md` arrives as an
|
|
2372
|
+
* ordinary file whose frontmatter is its own — reading it as the source's
|
|
2373
|
+
* name had the preview contradict the copy it was previewing;
|
|
2374
|
+
* - `landing`: the folder rules the bytes WILL be, when they land under the
|
|
2375
|
+
* name `access.md`. Parsed as the destination path, so the directory it
|
|
2376
|
+
* governs is already the right one. An `access.md` that will not parse
|
|
2377
|
+
* travels as nothing, as the loader treats it.
|
|
2378
|
+
*
|
|
2379
|
+
* `lstat`, not `stat`: a SYMLINK is none of this. `move_file` refuses a
|
|
2380
|
+
* symlink source outright, so a link to a directory must not be previewed
|
|
2381
|
+
* as a folder taking access files along, and nothing is read THROUGH the
|
|
2382
|
+
* link either — the link's target is not what would move.
|
|
2383
|
+
*
|
|
2384
|
+
* Only absence is an answer (see `isAbsence`): a probe that failed on
|
|
2385
|
+
* permissions or I/O does not say "this is a file", and reading a folder as
|
|
2386
|
+
* one would resolve it through frontmatter it does not have.
|
|
2387
|
+
*/
|
|
2388
|
+
private async relocationSubject(
|
|
2389
|
+
repoDir: string,
|
|
2390
|
+
fromPath: string,
|
|
2391
|
+
toPath: string,
|
|
2392
|
+
): Promise<{
|
|
2393
|
+
isFolder: boolean;
|
|
2394
|
+
ownHere: OwnEntries | null;
|
|
2395
|
+
ownThere: OwnEntries | null;
|
|
2396
|
+
landing: AccessFile | null;
|
|
2397
|
+
}> {
|
|
2398
|
+
const nothing = { isFolder: false, ownHere: null, ownThere: null, landing: null };
|
|
2399
|
+
const fromStat = await fs.lstat(path.join(repoDir, fromPath)).catch((err: unknown) => {
|
|
2400
|
+
if (isAbsence(err)) return null;
|
|
2401
|
+
throw err;
|
|
2402
|
+
});
|
|
2403
|
+
if (fromStat?.isSymbolicLink() === true) return nothing;
|
|
2404
|
+
if (fromStat?.isDirectory() === true) return { ...nothing, isFolder: true };
|
|
2405
|
+
// `roles.yaml` carries no own-entries wherever it sits, exactly as
|
|
2406
|
+
// `readOwnEntries` has it; a read that fails for any reason is no rules,
|
|
2407
|
+
// which is what the gates would say about the same file.
|
|
2408
|
+
if (fromPath === 'roles.yaml' || toPath === 'roles.yaml') return nothing;
|
|
2409
|
+
let text: string;
|
|
2410
|
+
try {
|
|
2411
|
+
text = await fs.readFile(path.join(repoDir, fromPath), 'utf-8');
|
|
2412
|
+
} catch {
|
|
2413
|
+
return nothing;
|
|
2414
|
+
}
|
|
2415
|
+
const parsed = isAccessMdPath(toPath) ? parseAccessFile(text, toPath) : null;
|
|
2416
|
+
return {
|
|
2417
|
+
isFolder: false,
|
|
2418
|
+
ownHere: ownEntriesFromText(text, fromPath),
|
|
2419
|
+
ownThere: ownEntriesFromText(text, toPath),
|
|
2420
|
+
landing: parsed?.ok === true ? parsed.file : null,
|
|
2421
|
+
};
|
|
2224
2422
|
}
|
|
2225
2423
|
|
|
2226
2424
|
/**
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { AuthUser, ChangeRequest } from '@bevel-software/platform-shared';
|
|
2
|
+
import type { Verb } from '../access-model/access-grammar.js';
|
|
3
|
+
import { accessMdPathForFolder, type TargetKind } from './access-mutation.service.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The ITEM a request is about — a folder, or a file that carries its own
|
|
7
|
+
* rules. Everything downstream (the branch name, the file spliced, the
|
|
8
|
+
* permission checked) is derived from this pair, so a request on a file can
|
|
9
|
+
* never reach beyond that file.
|
|
10
|
+
*/
|
|
11
|
+
export interface AccessRequestTarget {
|
|
12
|
+
/** Repo-relative path of the item itself — NOT of its rules file. */
|
|
13
|
+
path: string;
|
|
14
|
+
kind: TargetKind;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** A folder target, spelled once. */
|
|
18
|
+
export function folderTarget(repoRelFolder: string): AccessRequestTarget {
|
|
19
|
+
return { path: repoRelFolder, kind: 'folder' };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The file whose rules a request on this item edits: a folder's `access.md`,
|
|
24
|
+
* or the file itself (its own frontmatter). The one place the two cases part,
|
|
25
|
+
* so a file request writes nothing a sibling can see.
|
|
26
|
+
*/
|
|
27
|
+
export function rulesFileFor(target: AccessRequestTarget): string {
|
|
28
|
+
return target.kind === 'folder' ? accessMdPathForFolder(target.path) : target.path;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The levels the Manage access dialog can ask for. `owner` covers `write`. */
|
|
32
|
+
export type RequestLevel = Extract<Verb, 'write' | 'owner'>;
|
|
33
|
+
export const REQUEST_LEVELS: readonly RequestLevel[] = ['write', 'owner'];
|
|
34
|
+
export function isRequestLevel(value: unknown): value is RequestLevel {
|
|
35
|
+
return typeof value === 'string' && (REQUEST_LEVELS as readonly string[]).includes(value);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The optional note a requester may attach, in characters. */
|
|
39
|
+
export const REQUEST_NOTE_MAX = 500;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* One grant a request's branch still proposes. Structurally the plugin
|
|
43
|
+
* module's `JoinProposal` — stated here so the access module can consume the
|
|
44
|
+
* request lifecycle without importing the module that implements it (the
|
|
45
|
+
* dependency runs library → access, and this keeps it that way).
|
|
46
|
+
*/
|
|
47
|
+
export interface AccessProposal {
|
|
48
|
+
verb: Verb;
|
|
49
|
+
id: string;
|
|
50
|
+
principal:
|
|
51
|
+
| { kind: 'user'; email: string; displayName: string }
|
|
52
|
+
| { kind: 'role'; role: string };
|
|
53
|
+
label: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** One open request, as the editors' surfaces render it. */
|
|
57
|
+
export interface AccessRequestSummary {
|
|
58
|
+
number: number;
|
|
59
|
+
branch: string;
|
|
60
|
+
requesterName: string;
|
|
61
|
+
createdAt: string;
|
|
62
|
+
proposals: AccessProposal[];
|
|
63
|
+
/** The requester's note, as plain text, when they gave one. */
|
|
64
|
+
note?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The request lifecycle, as the access module needs it: what a branch still
|
|
69
|
+
* proposes, the open requests for an item, and the settle-if-nothing-is-left
|
|
70
|
+
* check. Implemented by `JoinRequestsService`.
|
|
71
|
+
*
|
|
72
|
+
* `key` is what `joinBranchFor` was given when the branch was named — a
|
|
73
|
+
* plugin's name for a Subscribe request, and the ITEM'S OWN PATH for every
|
|
74
|
+
* request that comes from the Manage access dialog. Keying a skill folder's
|
|
75
|
+
* request on its path is what makes the skill page's "Request write access"
|
|
76
|
+
* and a Can edit request on that same folder one request rather than two.
|
|
77
|
+
*/
|
|
78
|
+
export interface IAccessRequestLifecycle {
|
|
79
|
+
/** Null ⇒ the branch could not be read; that is not "proposes nothing". */
|
|
80
|
+
proposalsOn(branch: string, target: AccessRequestTarget): Promise<AccessProposal[] | null>;
|
|
81
|
+
list(
|
|
82
|
+
key: string,
|
|
83
|
+
target: AccessRequestTarget,
|
|
84
|
+
crs: ChangeRequest[],
|
|
85
|
+
actor: AuthUser,
|
|
86
|
+
): Promise<AccessRequestSummary[]>;
|
|
87
|
+
reconcile(
|
|
88
|
+
key: string,
|
|
89
|
+
target: AccessRequestTarget,
|
|
90
|
+
cr: ChangeRequest,
|
|
91
|
+
actor: AuthUser,
|
|
92
|
+
): Promise<boolean>;
|
|
93
|
+
}
|