@bevel-software/platform-core-backend 0.20.0 → 0.22.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/catalog-cache-invalidation.d.ts +7 -2
- package/dist/core/catalog-cache-invalidation.d.ts.map +1 -1
- package/dist/core/catalog-cache-invalidation.js +8 -9
- package/dist/core/catalog-cache-invalidation.js.map +1 -1
- package/dist/core/core-ports.d.ts +19 -0
- 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 +2 -0
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +55 -107
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +41 -4
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +183 -82
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +74 -28
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +157 -20
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/core/public-config.d.ts +5 -1
- package/dist/core/public-config.d.ts.map +1 -1
- package/dist/core/public-config.js +7 -5
- package/dist/core/public-config.js.map +1 -1
- package/dist/core-config.d.ts +95 -1
- package/dist/core-config.d.ts.map +1 -1
- package/dist/core-config.js +24 -8
- package/dist/core-config.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +18 -1
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +19 -8
- 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 +2 -1
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +10 -10
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/change-read-gate.d.ts +6 -3
- package/dist/modules/access/change-read-gate.d.ts.map +1 -1
- package/dist/modules/access/change-read-gate.js +9 -6
- package/dist/modules/access/change-read-gate.js.map +1 -1
- package/dist/modules/access/creator-access.d.ts +4 -2
- package/dist/modules/access/creator-access.d.ts.map +1 -1
- package/dist/modules/access/creator-access.js +7 -4
- package/dist/modules/access/creator-access.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/agent-instructions/read-preamble.d.ts +2 -1
- package/dist/modules/agent-instructions/read-preamble.d.ts.map +1 -1
- package/dist/modules/agent-instructions/read-preamble.js +6 -9
- package/dist/modules/agent-instructions/read-preamble.js.map +1 -1
- package/dist/modules/auth/account-admission.d.ts +40 -0
- package/dist/modules/auth/account-admission.d.ts.map +1 -0
- package/dist/modules/auth/account-admission.js +17 -0
- package/dist/modules/auth/account-admission.js.map +1 -0
- package/dist/modules/auth/account.routes.d.ts.map +1 -1
- package/dist/modules/auth/account.routes.js +4 -1
- package/dist/modules/auth/account.routes.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +45 -2
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +44 -3
- 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 +9 -0
- package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.d.ts +13 -1
- package/dist/modules/connection-probe/connection-probe.service.d.ts.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.js +10 -2
- package/dist/modules/connection-probe/connection-probe.service.js.map +1 -1
- package/dist/modules/database/advisory-lock.d.ts +17 -0
- package/dist/modules/database/advisory-lock.d.ts.map +1 -1
- package/dist/modules/database/advisory-lock.js +24 -3
- package/dist/modules/database/advisory-lock.js.map +1 -1
- package/dist/modules/database/connection.d.ts +67 -2
- package/dist/modules/database/connection.d.ts.map +1 -1
- package/dist/modules/database/connection.js +118 -12
- package/dist/modules/database/connection.js.map +1 -1
- package/dist/modules/database/migrate.d.ts +1 -1
- package/dist/modules/database/migrate.d.ts.map +1 -1
- package/dist/modules/database/migrate.js +27 -3
- package/dist/modules/database/migrate.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 +0 -1
- package/dist/modules/kb-fs/branch-name.d.ts.map +1 -1
- package/dist/modules/kb-fs/branch-name.js +3 -3
- package/dist/modules/kb-fs/branch-name.js.map +1 -1
- package/dist/modules/kb-fs/clone-config.d.ts +9 -8
- package/dist/modules/kb-fs/clone-config.d.ts.map +1 -1
- package/dist/modules/kb-fs/clone-config.js +14 -12
- package/dist/modules/kb-fs/clone-config.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/mcp/mcp.service.d.ts +7 -0
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +4 -4
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts +4 -4
- package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts.map +1 -1
- package/dist/modules/plugins/compile/marketplace-compiler.service.js +7 -10
- package/dist/modules/plugins/compile/marketplace-compiler.service.js.map +1 -1
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.d.ts +4 -1
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.d.ts.map +1 -1
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js +5 -3
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js.map +1 -1
- package/dist/modules/plugins/discovery/kb-plugin-source.d.ts +6 -1
- package/dist/modules/plugins/discovery/kb-plugin-source.d.ts.map +1 -1
- package/dist/modules/plugins/discovery/kb-plugin-source.js +14 -9
- package/dist/modules/plugins/discovery/kb-plugin-source.js.map +1 -1
- package/dist/modules/plugins/discovery/native.source.d.ts +4 -1
- package/dist/modules/plugins/discovery/native.source.d.ts.map +1 -1
- package/dist/modules/plugins/discovery/native.source.js +4 -2
- package/dist/modules/plugins/discovery/native.source.js.map +1 -1
- package/dist/modules/plugins/index.d.ts +1 -1
- package/dist/modules/plugins/index.d.ts.map +1 -1
- package/dist/modules/plugins/index.js +1 -1
- package/dist/modules/plugins/index.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-request-jobs.service.d.ts +3 -1
- package/dist/modules/plugins/join-request-jobs.service.d.ts.map +1 -1
- package/dist/modules/plugins/join-request-jobs.service.js +6 -6
- package/dist/modules/plugins/join-request-jobs.service.js.map +1 -1
- package/dist/modules/plugins/join-requests.service.d.ts +113 -18
- package/dist/modules/plugins/join-requests.service.d.ts.map +1 -1
- package/dist/modules/plugins/join-requests.service.js +178 -33
- package/dist/modules/plugins/join-requests.service.js.map +1 -1
- package/dist/modules/plugins/plugin-links.d.ts +65 -10
- package/dist/modules/plugins/plugin-links.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-links.js +136 -31
- package/dist/modules/plugins/plugin-links.js.map +1 -1
- package/dist/modules/plugins/plugin-links.service.d.ts +107 -5
- package/dist/modules/plugins/plugin-links.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-links.service.js +151 -21
- package/dist/modules/plugins/plugin-links.service.js.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.d.ts +10 -3
- package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.js +33 -26
- package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
- package/dist/modules/plugins/plugin-rename.service.d.ts +4 -2
- package/dist/modules/plugins/plugin-rename.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-rename.service.js +10 -12
- package/dist/modules/plugins/plugin-rename.service.js.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +3 -0
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +48 -17
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/plugins/plugins.service.d.ts +4 -4
- package/dist/modules/plugins/plugins.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.service.js +7 -10
- package/dist/modules/plugins/plugins.service.js.map +1 -1
- package/dist/modules/plugins/teams.routes.d.ts +4 -1
- package/dist/modules/plugins/teams.routes.d.ts.map +1 -1
- package/dist/modules/plugins/teams.routes.js +11 -9
- package/dist/modules/plugins/teams.routes.js.map +1 -1
- package/dist/modules/secrets-vault/index.d.ts +1 -1
- package/dist/modules/secrets-vault/index.d.ts.map +1 -1
- package/dist/modules/secrets-vault/index.js +1 -1
- package/dist/modules/secrets-vault/index.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-variable-loader.d.ts +29 -11
- package/dist/modules/secrets-vault/secrets-variable-loader.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-variable-loader.js +46 -21
- package/dist/modules/secrets-vault/secrets-variable-loader.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +3 -0
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.js +4 -6
- package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.d.ts +34 -9
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +113 -26
- 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 +34 -4
- package/dist/modules/settings/setup.routes.d.ts.map +1 -1
- package/dist/modules/settings/setup.routes.js +198 -18
- package/dist/modules/settings/setup.routes.js.map +1 -1
- package/dist/modules/skills/allowed-tools-check.d.ts +4 -3
- package/dist/modules/skills/allowed-tools-check.d.ts.map +1 -1
- package/dist/modules/skills/allowed-tools-check.js +8 -8
- package/dist/modules/skills/allowed-tools-check.js.map +1 -1
- package/dist/modules/skills/pending-skills.service.d.ts +3 -1
- package/dist/modules/skills/pending-skills.service.d.ts.map +1 -1
- package/dist/modules/skills/pending-skills.service.js +9 -6
- package/dist/modules/skills/pending-skills.service.js.map +1 -1
- package/dist/modules/skills/skill-access-requests.routes.d.ts +8 -9
- package/dist/modules/skills/skill-access-requests.routes.d.ts.map +1 -1
- package/dist/modules/skills/skill-access-requests.routes.js +26 -65
- package/dist/modules/skills/skill-access-requests.routes.js.map +1 -1
- package/dist/modules/skills/skills.routes.d.ts +4 -1
- package/dist/modules/skills/skills.routes.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.js +4 -4
- package/dist/modules/skills/skills.routes.js.map +1 -1
- package/dist/modules/skills/skills.service.d.ts +4 -2
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +13 -11
- package/dist/modules/skills/skills.service.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-manuals/mcp-json-discovery.d.ts +2 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.js +6 -5
- package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts +4 -2
- package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.js +12 -10
- package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -1
- package/dist/modules/tool-manuals/pending-tools.service.d.ts +3 -1
- package/dist/modules/tool-manuals/pending-tools.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/pending-tools.service.js +19 -16
- package/dist/modules/tool-manuals/pending-tools.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-delete.service.d.ts +4 -2
- package/dist/modules/tool-manuals/tool-delete.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-delete.service.js +14 -12
- package/dist/modules/tool-manuals/tool-delete.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +3 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.js +7 -7
- package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +4 -2
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +15 -12
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +3 -0
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +4 -6
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts +3 -2
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +47 -11
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +14 -6
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +40 -18
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/node-git-runner.d.ts +26 -2
- package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -1
- package/dist/modules/workflow/git/node-git-runner.js +40 -5
- 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/review-workflow/review-workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js +9 -4
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
- package/dist/modules/workflow/session-ontology.service.d.ts +6 -1
- package/dist/modules/workflow/session-ontology.service.d.ts.map +1 -1
- package/dist/modules/workflow/session-ontology.service.js +6 -2
- package/dist/modules/workflow/session-ontology.service.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 +42 -10
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +181 -71
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/session-ontology.gate.d.ts +3 -1
- package/dist/modules/workspace/session-ontology.gate.d.ts.map +1 -1
- package/dist/modules/workspace/session-ontology.gate.js +3 -3
- package/dist/modules/workspace/session-ontology.gate.js.map +1 -1
- package/dist/modules/workspace/startup/kb-git.d.ts +4 -3
- package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-git.js +15 -13
- package/dist/modules/workspace/startup/kb-git.js.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts +70 -7
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.js +183 -38
- package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts +7 -1
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js +23 -16
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/personal-spaces.step.d.ts +7 -1
- package/dist/modules/workspace/startup/steps/personal-spaces.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/personal-spaces.step.js +12 -5
- package/dist/modules/workspace/startup/steps/personal-spaces.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.d.ts +7 -1
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.js +12 -5
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts +7 -1
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.js +12 -5
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts +4 -1
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js +14 -11
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts +17 -3
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.js +36 -30
- package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.d.ts +6 -1
- package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.js +10 -6
- package/dist/modules/workspace/startup/steps/template-source.js.map +1 -1
- package/dist/modules/workspace/workspace.routes.d.ts +2 -1
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +8 -7
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +18 -12
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +49 -29
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +2 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +48 -20
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/modules/workspace/write-denial.d.ts +3 -2
- package/dist/modules/workspace/write-denial.d.ts.map +1 -1
- package/dist/modules/workspace/write-denial.js +6 -5
- package/dist/modules/workspace/write-denial.js.map +1 -1
- package/dist/shared/domain-errors.d.ts +39 -0
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +55 -0
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/git.contract.d.ts +62 -7
- package/dist/shared/git.contract.d.ts.map +1 -1
- package/dist/shared/git.contract.js +30 -8
- package/dist/shared/git.contract.js.map +1 -1
- package/dist/shared/kb-context.d.ts +76 -0
- package/dist/shared/kb-context.d.ts.map +1 -0
- package/dist/shared/kb-context.js +108 -0
- package/dist/shared/kb-context.js.map +1 -0
- package/dist/shared/kb-layout.d.ts +3 -2
- package/dist/shared/kb-layout.d.ts.map +1 -1
- package/dist/shared/kb-layout.js +4 -3
- package/dist/shared/kb-layout.js.map +1 -1
- package/dist/shared/kb-layout.test.js +35 -27
- package/dist/shared/kb-layout.test.js.map +1 -1
- package/dist/shared/pending-proposals.d.ts +4 -1
- package/dist/shared/pending-proposals.d.ts.map +1 -1
- package/dist/shared/pending-proposals.js +3 -4
- package/dist/shared/pending-proposals.js.map +1 -1
- package/dist/shared/redact-secret.d.ts +5 -5
- package/dist/shared/redact-secret.js +5 -5
- package/dist/tenancy/index.d.ts +13 -0
- package/dist/tenancy/index.d.ts.map +1 -0
- package/dist/tenancy/index.js +13 -0
- package/dist/tenancy/index.js.map +1 -0
- package/dist/tenancy/static-tenant-source.d.ts +98 -0
- package/dist/tenancy/static-tenant-source.d.ts.map +1 -0
- package/dist/tenancy/static-tenant-source.js +220 -0
- package/dist/tenancy/static-tenant-source.js.map +1 -0
- package/dist/tenancy/tenant-host.d.ts +80 -0
- package/dist/tenancy/tenant-host.d.ts.map +1 -0
- package/dist/tenancy/tenant-host.js +217 -0
- package/dist/tenancy/tenant-host.js.map +1 -0
- package/dist/tenancy/tenant-runtime.d.ts +91 -0
- package/dist/tenancy/tenant-runtime.d.ts.map +1 -0
- package/dist/tenancy/tenant-runtime.js +174 -0
- package/dist/tenancy/tenant-runtime.js.map +1 -0
- package/dist/tenancy/tenant-secrets.d.ts +24 -0
- package/dist/tenancy/tenant-secrets.d.ts.map +1 -0
- package/dist/tenancy/tenant-secrets.js +30 -0
- package/dist/tenancy/tenant-secrets.js.map +1 -0
- package/dist/tenancy/tenant-source.contract.d.ts +45 -0
- package/dist/tenancy/tenant-source.contract.d.ts.map +1 -0
- package/dist/tenancy/tenant-source.contract.js +21 -0
- package/dist/tenancy/tenant-source.contract.js.map +1 -0
- package/migrations/0000_core_init.sql +14 -14
- package/migrations/0003_deployment_settings.sql +1 -1
- package/migrations/0005_github_facade.sql +1 -1
- package/migrations/0012_agent_audit.sql +6 -6
- package/package.json +3 -3
- package/src/__tests__/branch-model.test.ts +7 -6
- package/src/__tests__/kb-context.ts +30 -0
- package/src/__tests__/kb-layout-config.test.ts +35 -30
- package/src/core/__tests__/catalog-cache-invalidation.test.ts +2 -1
- package/src/core/__tests__/lifecycle.test.ts +78 -1
- package/src/core/__tests__/public-config.test.ts +5 -1
- package/src/core/catalog-cache-invalidation.ts +16 -11
- package/src/core/core-ports.ts +19 -0
- package/src/core/create-core-server.ts +64 -107
- package/src/core/create-core-services.ts +238 -96
- package/src/core/lifecycle.ts +214 -29
- package/src/core/public-config.ts +8 -5
- package/src/core-config.ts +107 -8
- package/src/index.ts +25 -2
- package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +2 -1
- 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.batch.test.ts +2 -1
- package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +2 -1
- package/src/modules/access/__tests__/access.routes.group-grant.test.ts +2 -1
- package/src/modules/access/__tests__/access.routes.overrides.test.ts +2 -1
- package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +2 -1
- package/src/modules/access/__tests__/access.routes.prospective.test.ts +2 -1
- package/src/modules/access/__tests__/access.routes.revoke.test.ts +2 -1
- package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +2 -1
- package/src/modules/access/__tests__/change-read-gate.test.ts +13 -11
- package/src/modules/access/__tests__/creator-access.test.ts +3 -2
- package/src/modules/access/__tests__/roles.routes.test.ts +2 -1
- package/src/modules/access/access-control.service.ts +20 -6
- 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 +10 -13
- package/src/modules/access/change-read-gate.ts +10 -4
- package/src/modules/access/creator-access.ts +7 -2
- package/src/modules/access-model/access-grammar.ts +22 -0
- package/src/modules/agent-instructions/__tests__/read-preamble.test.ts +13 -12
- package/src/modules/agent-instructions/read-preamble.ts +10 -12
- package/src/modules/auth/__tests__/account.routes.test.ts +15 -0
- package/src/modules/auth/__tests__/auth.service.test.ts +98 -0
- package/src/modules/auth/account-admission.ts +44 -0
- package/src/modules/auth/account.routes.ts +4 -1
- package/src/modules/auth/auth.service.ts +61 -2
- package/src/modules/auth/oidc-auth-provider.ts +9 -0
- package/src/modules/connection-probe/connection-probe.service.ts +7 -1
- package/src/modules/database/__tests__/advisory-lock.test.ts +23 -1
- package/src/modules/database/__tests__/connection.test.ts +98 -0
- package/src/modules/database/__tests__/migrations-unqualified.test.ts +32 -0
- package/src/modules/database/advisory-lock.ts +28 -4
- package/src/modules/database/connection.ts +143 -13
- package/src/modules/database/migrate.ts +48 -14
- package/src/modules/diff/__tests__/diff.service.seed-atomicity.test.ts +2 -1
- package/src/modules/diff/__tests__/diff.service.test.ts +2 -1
- 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 +6 -6
- package/src/modules/kb-fs/__tests__/clone-config.test.ts +24 -20
- package/src/modules/kb-fs/__tests__/remote-url.test.ts +38 -0
- package/src/modules/kb-fs/branch-name.ts +3 -3
- package/src/modules/kb-fs/clone-config.ts +15 -12
- package/src/modules/kb-fs/remote-url.ts +35 -0
- package/src/modules/marketplace/__tests__/marketplace-repo.head.test.ts +2 -1
- package/src/modules/mcp/mcp.service.ts +11 -4
- package/src/modules/plugins/__tests__/bundle-dialect.e2e.test.ts +12 -11
- package/src/modules/plugins/__tests__/bundle-dialect.test.ts +2 -1
- package/src/modules/plugins/__tests__/compile-marketplace.test.ts +6 -4
- package/src/modules/plugins/__tests__/join-proposals.test.ts +100 -19
- package/src/modules/plugins/__tests__/join-request-jobs.service.test.ts +2 -1
- package/src/modules/plugins/__tests__/join-requests.service.test.ts +28 -11
- package/src/modules/plugins/__tests__/join-requests.settlement.test.ts +371 -0
- package/src/modules/plugins/__tests__/plugin-creation-display-name.test.ts +4 -3
- package/src/modules/plugins/__tests__/plugin-index.service.test.ts +6 -4
- package/src/modules/plugins/__tests__/plugin-links.service.test.ts +220 -5
- package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +3 -1
- package/src/modules/plugins/__tests__/plugin-rename.service.test.ts +7 -6
- package/src/modules/plugins/__tests__/plugins.routes.test.ts +12 -3
- package/src/modules/plugins/__tests__/teams.routes.test.ts +2 -1
- package/src/modules/plugins/compile/marketplace-compiler.service.ts +7 -9
- package/src/modules/plugins/discovery/__tests__/kb-plugin-source.test.ts +20 -16
- package/src/modules/plugins/discovery/bundle-dialect/bundle.source.ts +9 -2
- package/src/modules/plugins/discovery/kb-plugin-source.ts +15 -8
- package/src/modules/plugins/discovery/native.source.ts +4 -1
- package/src/modules/plugins/index.ts +1 -1
- package/src/modules/plugins/join-proposals.ts +87 -19
- package/src/modules/plugins/join-request-jobs.service.ts +8 -7
- package/src/modules/plugins/join-requests.service.ts +203 -43
- package/src/modules/plugins/plugin-links.service.ts +192 -19
- package/src/modules/plugins/plugin-links.ts +147 -27
- package/src/modules/plugins/plugin-provision.service.ts +54 -27
- package/src/modules/plugins/plugin-rename.service.ts +10 -10
- package/src/modules/plugins/plugins.routes.ts +48 -17
- package/src/modules/plugins/plugins.service.ts +7 -11
- package/src/modules/plugins/teams.routes.ts +16 -8
- package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +7 -0
- package/src/modules/secrets-vault/__tests__/secrets-variable-loader.test.ts +40 -0
- package/src/modules/secrets-vault/__tests__/tool-owner-gate.route.test.ts +2 -0
- package/src/modules/secrets-vault/index.ts +2 -0
- package/src/modules/secrets-vault/secrets-variable-loader.ts +51 -21
- package/src/modules/secrets-vault/secrets-vault.routes.ts +7 -6
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +24 -11
- package/src/modules/settings/__tests__/git-root-folders.test.ts +2 -1
- package/src/modules/settings/__tests__/repository-source.test.ts +191 -0
- package/src/modules/settings/__tests__/setup.routes.boot-failure.test.ts +2 -1
- package/src/modules/settings/__tests__/setup.routes.git-mode.test.ts +353 -0
- package/src/modules/settings/__tests__/setup.routes.github-app.test.ts +282 -0
- package/src/modules/settings/__tests__/setup.routes.layout-phase.test.ts +7 -4
- package/src/modules/settings/__tests__/setup.routes.managed-phase.test.ts +233 -0
- package/src/modules/settings/__tests__/setup.routes.oidc.test.ts +2 -0
- package/src/modules/settings/__tests__/setup.routes.sync-status.test.ts +2 -0
- package/src/modules/settings/__tests__/setup.routes.test.ts +33 -52
- package/src/modules/settings/deployment-settings.service.ts +130 -23
- 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 +233 -19
- package/src/modules/skills/__tests__/allowed-tools-check.test.ts +11 -10
- package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +4 -3
- package/src/modules/skills/__tests__/pending-skills.service.test.ts +3 -1
- package/src/modules/skills/__tests__/skill-access-requests.routes.test.ts +7 -2
- package/src/modules/skills/__tests__/skills.routes.warnings.test.ts +2 -1
- package/src/modules/skills/__tests__/skills.service.test.ts +3 -2
- package/src/modules/skills/allowed-tools-check.ts +7 -6
- package/src/modules/skills/pending-skills.service.ts +9 -8
- package/src/modules/skills/skill-access-requests.routes.ts +29 -79
- package/src/modules/skills/skills.routes.ts +4 -3
- package/src/modules/skills/skills.service.ts +13 -9
- package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +3 -2
- package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +3 -2
- package/src/modules/tool-helpers/tool-context.ts +15 -0
- package/src/modules/tool-manuals/__tests__/mcp-json-discovery.test.ts +17 -12
- package/src/modules/tool-manuals/__tests__/mcp-server-edit.service.test.ts +2 -1
- package/src/modules/tool-manuals/__tests__/pending-tools.service.test.ts +4 -1
- package/src/modules/tool-manuals/__tests__/tool-delete.service.test.ts +2 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +2 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.cli.test.ts +5 -4
- package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +2 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.invalid.routes.test.ts +4 -2
- package/src/modules/tool-manuals/__tests__/tool-manuals.invalid.test.ts +13 -5
- package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +2 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.restart.test.ts +9 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +3 -2
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +2 -0
- package/src/modules/tool-manuals/mcp-json-discovery.ts +6 -4
- package/src/modules/tool-manuals/mcp-server-edit.service.ts +13 -11
- package/src/modules/tool-manuals/pending-tools.service.ts +19 -16
- package/src/modules/tool-manuals/tool-delete.service.ts +13 -10
- package/src/modules/tool-manuals/tool-manuals.routes.ts +10 -8
- package/src/modules/tool-manuals/tool-manuals.service.ts +15 -13
- package/src/modules/tool-manuals/tool-manuals.tools.ts +7 -6
- package/src/modules/workflow/__tests__/apply-failure.test.ts +11 -2
- package/src/modules/workflow/__tests__/merge-announces-tree.test.ts +14 -26
- package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +2 -1
- package/src/modules/workflow/__tests__/session-ontology.service.test.ts +3 -2
- package/src/modules/workflow/__tests__/workflow.routes.lock-path.test.ts +2 -1
- package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +3 -10
- package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +2 -1
- package/src/modules/workflow/__tests__/workflow.service.deleted-branch-sweep.test.ts +3 -10
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +187 -35
- package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +2 -1
- package/src/modules/workflow/__tests__/workflow.service.platform-restore.test.ts +2 -1
- package/src/modules/workflow/__tests__/workflow.service.read-gate.test.ts +3 -2
- package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +2 -1
- package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +5 -4
- package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +148 -41
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +143 -2
- package/src/modules/workflow/agent-tools/workflow.tools.ts +60 -12
- package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +5 -4
- package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +12 -11
- package/src/modules/workflow/git/__tests__/git.service.commitChanges.test.ts +9 -8
- package/src/modules/workflow/git/__tests__/git.service.commitFile.strayPath.test.ts +8 -7
- package/src/modules/workflow/git/__tests__/git.service.commitFile.test.ts +6 -5
- package/src/modules/workflow/git/__tests__/git.service.createBranch.test.ts +3 -2
- package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +3 -2
- package/src/modules/workflow/git/__tests__/git.service.diffFileAtCommit.test.ts +7 -6
- package/src/modules/workflow/git/__tests__/git.service.diffFileBetweenBranches.test.ts +16 -15
- package/src/modules/workflow/git/__tests__/git.service.fileContentsAtCommit.test.ts +3 -2
- package/src/modules/workflow/git/__tests__/git.service.forkPoint.test.ts +2 -1
- package/src/modules/workflow/git/__tests__/git.service.history-guards.test.ts +2 -1
- package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +10 -9
- package/src/modules/workflow/git/__tests__/git.service.listTrackedFiles.test.ts +2 -1
- package/src/modules/workflow/git/__tests__/git.service.logForFile.test.ts +6 -5
- package/src/modules/workflow/git/__tests__/git.service.mergeChangeRequest.test.ts +9 -8
- package/src/modules/workflow/git/__tests__/git.service.pendingChanges.test.ts +2 -1
- package/src/modules/workflow/git/__tests__/git.service.postMergePull.test.ts +4 -3
- package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +130 -8
- package/src/modules/workflow/git/__tests__/git.service.readFileAtRef.test.ts +2 -1
- package/src/modules/workflow/git/__tests__/git.service.revertPathsAndPush.test.ts +2 -1
- package/src/modules/workflow/git/__tests__/git.service.status.test.ts +4 -3
- package/src/modules/workflow/git/__tests__/git.service.syncFromRemote.test.ts +8 -7
- package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +41 -3
- 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 +49 -17
- package/src/modules/workflow/git/node-git-runner.ts +43 -4
- package/src/modules/workflow/git/pull-request.service.ts +119 -0
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +9 -6
- package/src/modules/workflow/session-ontology.service.ts +7 -2
- package/src/modules/workflow/workflow.routes.ts +3 -1
- package/src/modules/workflow/workflow.service.ts +201 -76
- package/src/modules/workspace/__tests__/file-stat-access.test.ts +3 -2
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +10 -9
- package/src/modules/workspace/__tests__/session-ontology.gate.test.ts +4 -2
- package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +3 -2
- package/src/modules/workspace/__tests__/workspace.routes.branch-errors.test.ts +2 -1
- package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +2 -1
- package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +2 -1
- package/src/modules/workspace/__tests__/workspace.routes.download-implies-read.test.ts +2 -1
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +2 -1
- package/src/modules/workspace/__tests__/workspace.routes.file-writes.test.ts +2 -1
- package/src/modules/workspace/__tests__/workspace.routes.folders.test.ts +5 -4
- package/src/modules/workspace/__tests__/workspace.routes.move-platform-files.test.ts +2 -1
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +2 -1
- package/src/modules/workspace/__tests__/workspace.service.ensure-remotes-fetched.test.ts +2 -2
- package/src/modules/workspace/__tests__/workspace.service.list-cloned.test.ts +6 -5
- package/src/modules/workspace/__tests__/workspace.service.move-never-overwrites.test.ts +2 -1
- package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +2 -1
- package/src/modules/workspace/__tests__/workspace.service.test.ts +84 -50
- package/src/modules/workspace/__tests__/workspace.service.unknown-branch.test.ts +45 -1
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +10 -7
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +237 -10
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +44 -11
- package/src/modules/workspace/__tests__/write-denial.test.ts +5 -4
- package/src/modules/workspace/session-ontology.gate.ts +6 -4
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.degraded.test.ts +6 -2
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +240 -21
- package/src/modules/workspace/startup/kb-git.ts +15 -22
- package/src/modules/workspace/startup/kb-startup-runner.ts +198 -45
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +104 -98
- package/src/modules/workspace/startup/steps/groups-to-plugins.step.ts +24 -16
- package/src/modules/workspace/startup/steps/personal-spaces.step.ts +14 -5
- package/src/modules/workspace/startup/steps/plugin-display-names.step.ts +13 -5
- package/src/modules/workspace/startup/steps/plugin-manifests.step.ts +14 -5
- package/src/modules/workspace/startup/steps/seed-tree.ts +11 -9
- package/src/modules/workspace/startup/steps/template-files.step.ts +43 -34
- package/src/modules/workspace/startup/steps/template-source.ts +8 -5
- package/src/modules/workspace/workspace.routes.ts +8 -7
- package/src/modules/workspace/workspace.service.ts +47 -28
- package/src/modules/workspace/workspace.tools.ts +56 -20
- package/src/modules/workspace/write-denial.ts +7 -5
- package/src/shared/__tests__/kb-layout.plugin.test.ts +19 -16
- package/src/shared/domain-errors.ts +58 -0
- package/src/shared/git.contract.ts +80 -8
- package/src/shared/kb-context.ts +130 -0
- package/src/shared/kb-layout.test.ts +37 -27
- package/src/shared/kb-layout.ts +4 -3
- package/src/shared/pending-proposals.ts +7 -4
- package/src/shared/redact-secret.ts +5 -5
- package/src/tenancy/__tests__/static-tenant-source.test.ts +177 -0
- package/src/tenancy/__tests__/tenant-host.test.ts +372 -0
- package/src/tenancy/__tests__/tenant-runtime.test.ts +221 -0
- package/src/tenancy/__tests__/tenant-secrets.test.ts +35 -0
- package/src/tenancy/index.ts +35 -0
- package/src/tenancy/static-tenant-source.ts +296 -0
- package/src/tenancy/tenant-host.ts +261 -0
- package/src/tenancy/tenant-runtime.ts +208 -0
- package/src/tenancy/tenant-secrets.ts +41 -0
- package/src/tenancy/tenant-source.contract.ts +59 -0
|
@@ -6,6 +6,7 @@ import { DEFAULT_BRANCH } from '@bevel-software/platform-shared';
|
|
|
6
6
|
import { workspaceIdForBranch } from '../../../shared/workspace-id.js';
|
|
7
7
|
import { readAgentPreamble, type PreambleWorkspace } from '../read-preamble.js';
|
|
8
8
|
import { NodeFs } from '../../kb-fs/node-fs.js';
|
|
9
|
+
import { testKbContext } from '../../../__tests__/kb-context.js';
|
|
9
10
|
|
|
10
11
|
/** The one disk, as production wires it. */
|
|
11
12
|
const disk = new NodeFs();
|
|
@@ -42,7 +43,7 @@ describe('readAgentPreamble', () => {
|
|
|
42
43
|
it('reads the default branch copy at <kbDirName>/mcp-description.md, creating the workspace first', async () => {
|
|
43
44
|
await fs.writeFile(path.join(wsDir, KB, 'mcp-description.md'), 'Acme.\n', 'utf8');
|
|
44
45
|
const ws = workspace();
|
|
45
|
-
expect(await readAgentPreamble(ws, KB, disk)).toBe('Acme.\n');
|
|
46
|
+
expect(await readAgentPreamble(ws, testKbContext({ kbDirName: KB }), disk)).toBe('Acme.\n');
|
|
46
47
|
expect(ws.getOrCreateForBranch).toHaveBeenCalledWith(DEFAULT_BRANCH);
|
|
47
48
|
expect(ws.getWorkspacePath).toHaveBeenCalledWith(workspaceIdForBranch(DEFAULT_BRANCH));
|
|
48
49
|
});
|
|
@@ -50,19 +51,19 @@ describe('readAgentPreamble', () => {
|
|
|
50
51
|
it("spells the path with the deployment's own KB dir name", async () => {
|
|
51
52
|
await fs.mkdir(path.join(wsDir, 'kb'), { recursive: true });
|
|
52
53
|
await fs.writeFile(path.join(wsDir, 'kb', 'mcp-description.md'), 'Renamed root.', 'utf8');
|
|
53
|
-
expect(await readAgentPreamble(workspace(), 'kb', disk)).toBe('Renamed root.');
|
|
54
|
-
expect(await readAgentPreamble(workspace(), KB, disk)).toBeNull(); // the default-named root has none
|
|
54
|
+
expect(await readAgentPreamble(workspace(), testKbContext({ kbDirName: 'kb' }), disk)).toBe('Renamed root.');
|
|
55
|
+
expect(await readAgentPreamble(workspace(), testKbContext({ kbDirName: KB }), disk)).toBeNull(); // the default-named root has none
|
|
55
56
|
});
|
|
56
57
|
|
|
57
58
|
it('a file that is not there is an absence, not a fault', async () => {
|
|
58
|
-
expect(await readAgentPreamble(workspace(), KB, disk)).toBeNull();
|
|
59
|
+
expect(await readAgentPreamble(workspace(), testKbContext({ kbDirName: KB }), disk)).toBeNull();
|
|
59
60
|
});
|
|
60
61
|
|
|
61
62
|
it('refuses a symlink: the secret it points at is never read, and it is not an absence either', async () => {
|
|
62
63
|
const secret = path.join(root, 'secret.txt');
|
|
63
64
|
await fs.writeFile(secret, 'DATABASE_URL=postgres://…', 'utf8');
|
|
64
65
|
await fs.symlink(secret, path.join(wsDir, KB, 'mcp-description.md'));
|
|
65
|
-
await expect(readAgentPreamble(workspace(), KB, disk)).rejects.toThrow(/symlink, not a regular file/);
|
|
66
|
+
await expect(readAgentPreamble(workspace(), testKbContext({ kbDirName: KB }), disk)).rejects.toThrow(/symlink, not a regular file/);
|
|
66
67
|
});
|
|
67
68
|
|
|
68
69
|
it('refuses a repository folder reached through a link — the whole path must be its own, not only the file', async () => {
|
|
@@ -72,7 +73,7 @@ describe('readAgentPreamble', () => {
|
|
|
72
73
|
await fs.writeFile(path.join(elsewhere, 'mcp-description.md'), 'Not the repository.', 'utf8');
|
|
73
74
|
await fs.rm(path.join(wsDir, KB), { recursive: true });
|
|
74
75
|
await fs.symlink(elsewhere, path.join(wsDir, KB), process.platform === 'win32' ? 'junction' : 'dir');
|
|
75
|
-
await expect(readAgentPreamble(workspace(), KB, disk)).rejects.toThrow(/through a symlink/);
|
|
76
|
+
await expect(readAgentPreamble(workspace(), testKbContext({ kbDirName: KB }), disk)).rejects.toThrow(/through a symlink/);
|
|
76
77
|
});
|
|
77
78
|
|
|
78
79
|
it('reads through a link ABOVE the workspace — a mounted volume is the operator\'s, not the repository\'s', async () => {
|
|
@@ -81,12 +82,12 @@ describe('readAgentPreamble', () => {
|
|
|
81
82
|
await fs.symlink(wsDir, mount, process.platform === 'win32' ? 'junction' : 'dir');
|
|
82
83
|
const ws = workspace();
|
|
83
84
|
ws.getWorkspacePath.mockImplementation(async () => mount);
|
|
84
|
-
expect(await readAgentPreamble(ws, KB, disk)).toBe('Acme.\n');
|
|
85
|
+
expect(await readAgentPreamble(ws, testKbContext({ kbDirName: KB }), disk)).toBe('Acme.\n');
|
|
85
86
|
});
|
|
86
87
|
|
|
87
88
|
it('refuses a directory squatting the name', async () => {
|
|
88
89
|
await fs.mkdir(path.join(wsDir, KB, 'mcp-description.md'));
|
|
89
|
-
await expect(readAgentPreamble(workspace(), KB, disk)).rejects.toThrow(/directory, not a regular file/);
|
|
90
|
+
await expect(readAgentPreamble(workspace(), testKbContext({ kbDirName: KB }), disk)).rejects.toThrow(/directory, not a regular file/);
|
|
90
91
|
});
|
|
91
92
|
|
|
92
93
|
it('a KB directory that is a regular file is an ABSENT preamble, the shared rule for "nothing can be there"', async () => {
|
|
@@ -96,7 +97,7 @@ describe('readAgentPreamble', () => {
|
|
|
96
97
|
// squatted this way is the startup phase's to refuse, loudly, once.
|
|
97
98
|
await fs.rm(path.join(wsDir, KB), { recursive: true });
|
|
98
99
|
await fs.writeFile(path.join(wsDir, KB), 'not a directory', 'utf8');
|
|
99
|
-
expect(await readAgentPreamble(workspace(), KB, disk)).toBeNull();
|
|
100
|
+
expect(await readAgentPreamble(workspace(), testKbContext({ kbDirName: KB }), disk)).toBeNull();
|
|
100
101
|
});
|
|
101
102
|
|
|
102
103
|
it('an lstat that fails for any reason but absence throws, never an empty preamble', async () => {
|
|
@@ -110,7 +111,7 @@ describe('readAgentPreamble', () => {
|
|
|
110
111
|
? Promise.reject(Object.assign(new Error('EACCES: permission denied'), { code: 'EACCES' }))
|
|
111
112
|
: (real as (p: string) => Promise<unknown>).call(fs, p)) as never);
|
|
112
113
|
try {
|
|
113
|
-
await expect(readAgentPreamble(workspace(), KB, disk)).rejects.toMatchObject({ code: 'EACCES' });
|
|
114
|
+
await expect(readAgentPreamble(workspace(), testKbContext({ kbDirName: KB }), disk)).rejects.toMatchObject({ code: 'EACCES' });
|
|
114
115
|
} finally {
|
|
115
116
|
spy.mockRestore();
|
|
116
117
|
}
|
|
@@ -121,7 +122,7 @@ describe('readAgentPreamble', () => {
|
|
|
121
122
|
const file = path.join(wsDir, KB, 'mcp-description.md');
|
|
122
123
|
await fs.writeFile(file, 'Acme.', 'utf8');
|
|
123
124
|
await fs.chmod(file, 0o000); // the lstat still passes; the open is what fails
|
|
124
|
-
await expect(readAgentPreamble(workspace(), KB, disk)).rejects.toMatchObject({ code: 'EACCES' });
|
|
125
|
+
await expect(readAgentPreamble(workspace(), testKbContext({ kbDirName: KB }), disk)).rejects.toMatchObject({ code: 'EACCES' });
|
|
125
126
|
});
|
|
126
127
|
|
|
127
128
|
it('a workspace that cannot be located throws too', async () => {
|
|
@@ -130,6 +131,6 @@ describe('readAgentPreamble', () => {
|
|
|
130
131
|
ws.getWorkspacePath.mockImplementation(async () => {
|
|
131
132
|
throw eio;
|
|
132
133
|
});
|
|
133
|
-
await expect(readAgentPreamble(ws, KB, disk)).rejects.toBe(eio);
|
|
134
|
+
await expect(readAgentPreamble(ws, testKbContext({ kbDirName: KB }), disk)).rejects.toBe(eio);
|
|
134
135
|
});
|
|
135
136
|
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
4
|
-
import { workspaceIdForBranch } from '../../shared/workspace-id.js';
|
|
3
|
+
import type { KbContext } from '../../shared/kb-context.js';
|
|
5
4
|
import { isAbsence, type IFsProbe } from '../../shared/fs.contract.js';
|
|
6
5
|
import type { WorkspaceService } from '../workspace/workspace.service.js';
|
|
7
6
|
import { PREAMBLE_FILE } from './compose.js';
|
|
@@ -43,11 +42,17 @@ export type PreambleWorkspace = Pick<WorkspaceService, 'getOrCreateForBranch' |
|
|
|
43
42
|
*/
|
|
44
43
|
export async function readAgentPreamble(
|
|
45
44
|
workspace: PreambleWorkspace,
|
|
46
|
-
|
|
45
|
+
kb: Pick<KbContext, 'kbDirName' | 'defaultBranch' | 'defaultWorkspaceId'>,
|
|
47
46
|
disk: IFsProbe,
|
|
48
47
|
): Promise<string | null> {
|
|
49
|
-
|
|
50
|
-
const
|
|
48
|
+
const { kbDirName, defaultBranch } = kb;
|
|
49
|
+
const notRegular = (kind: string): Error =>
|
|
50
|
+
new Error(
|
|
51
|
+
`${PREAMBLE_FILE} on branch "${defaultBranch}" is a ${kind}, not a regular file at its own place; ` +
|
|
52
|
+
'refusing to read it. Replace it with a plain markdown file in the repository folder.',
|
|
53
|
+
);
|
|
54
|
+
await workspace.getOrCreateForBranch(defaultBranch);
|
|
55
|
+
const wsDir = await workspace.getWorkspacePath(kb.defaultWorkspaceId());
|
|
51
56
|
const abs = path.join(wsDir, kbDirName, PREAMBLE_FILE);
|
|
52
57
|
|
|
53
58
|
const found = await disk.lstatOrNull(abs);
|
|
@@ -79,10 +84,3 @@ export async function readAgentPreamble(
|
|
|
79
84
|
await handle.close();
|
|
80
85
|
}
|
|
81
86
|
}
|
|
82
|
-
|
|
83
|
-
function notRegular(kind: string): Error {
|
|
84
|
-
return new Error(
|
|
85
|
-
`${PREAMBLE_FILE} on branch "${DEFAULT_BRANCH}" is a ${kind}, not a regular file at its own place; ` +
|
|
86
|
-
'refusing to read it. Replace it with a plain markdown file in the repository folder.',
|
|
87
|
-
);
|
|
88
|
-
}
|
|
@@ -3,6 +3,7 @@ import express from 'express';
|
|
|
3
3
|
import type { Server } from 'node:http';
|
|
4
4
|
import { createAccountRoutes } from '../account.routes.js';
|
|
5
5
|
import { AuthService } from '../auth.service.js';
|
|
6
|
+
import { AccountAdmissionRefusedError } from '../account-admission.js';
|
|
6
7
|
import { hashPassword } from '../password-hash.js';
|
|
7
8
|
import type { Database } from '../../database/connection.js';
|
|
8
9
|
import type { IAdminAccessService } from '../../admin/admin.interface.js';
|
|
@@ -145,6 +146,20 @@ describe('account routes — admin gate', () => {
|
|
|
145
146
|
expect(accountErasure.eraseUser).toHaveBeenLastCalledWith('u2');
|
|
146
147
|
});
|
|
147
148
|
|
|
149
|
+
it('an account the admission port refuses → 403 carrying the port\'s own words', async () => {
|
|
150
|
+
const base = await listen(makeApp({ admin: true }));
|
|
151
|
+
vi.mocked(authService.createAccount).mockRejectedValueOnce(
|
|
152
|
+
new AccountAdmissionRefusedError('No seat left on this plan'),
|
|
153
|
+
);
|
|
154
|
+
const refused = await fetch(`${base}/api/admin/accounts`, {
|
|
155
|
+
method: 'POST',
|
|
156
|
+
headers: { 'Content-Type': 'application/json' },
|
|
157
|
+
body: JSON.stringify({ email: 'b@example.com', password: 'long-enough-pw' }),
|
|
158
|
+
});
|
|
159
|
+
expect(refused.status).toBe(403);
|
|
160
|
+
expect(((await refused.json()) as { error: string }).error).toBe('No seat left on this plan');
|
|
161
|
+
});
|
|
162
|
+
|
|
148
163
|
it('erasure failure → 500 with a generic body (no internal error text)', async () => {
|
|
149
164
|
const base = await listen(makeApp({ admin: true }));
|
|
150
165
|
vi.mocked(accountErasure.eraseUser).mockRejectedValueOnce(
|
|
@@ -2,6 +2,7 @@ import { describe, expect, it, vi } from 'vitest';
|
|
|
2
2
|
import type { Database } from '../../database/connection.js';
|
|
3
3
|
import type { CoreConfig } from '../../../core-config.js';
|
|
4
4
|
import { AuthService } from '../auth.service.js';
|
|
5
|
+
import { AccountAdmissionRefusedError, type IAccountAdmission } from '../account-admission.js';
|
|
5
6
|
import { hashPassword, verifyPassword } from '../password-hash.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -573,6 +574,44 @@ describe('AuthService — the SSO domain allow-list', () => {
|
|
|
573
574
|
).rejects.toThrow(/domain is not allowed/i);
|
|
574
575
|
});
|
|
575
576
|
|
|
577
|
+
/**
|
|
578
|
+
* One rule per sign-in. A provider that decided for itself who may enter
|
|
579
|
+
* (an invitation, a claimed domain) is not overruled by a list the admin
|
|
580
|
+
* set for the deployment's own provider; the plan still has its say.
|
|
581
|
+
*/
|
|
582
|
+
it('is not laid on top of a provider that decided admission itself', async () => {
|
|
583
|
+
const { db } = makeFakeDb([[{ ...ROW, email: 'invited@gmail.com', name: 'Invited' }]]);
|
|
584
|
+
const out = await new AuthService(db, config).loginWithSso('invited@gmail.com', 'Invited', {
|
|
585
|
+
admittedByProvider: true,
|
|
586
|
+
});
|
|
587
|
+
expect(out.user.email).toBe('invited@gmail.com');
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
it('still asks the plan about someone the provider admitted', async () => {
|
|
591
|
+
const asked: Array<[string, string]> = [];
|
|
592
|
+
const plan: IAccountAdmission = {
|
|
593
|
+
canProvision: async (email, reason) => {
|
|
594
|
+
asked.push([email, reason]);
|
|
595
|
+
return { ok: false, message: 'No seat left on this plan' };
|
|
596
|
+
},
|
|
597
|
+
};
|
|
598
|
+
const { db } = makeFakeDb([[]]);
|
|
599
|
+
await expect(
|
|
600
|
+
new AuthService(db, config, plan).loginWithSso('invited@gmail.com', 'Invited', { admittedByProvider: true }),
|
|
601
|
+
).rejects.toBeInstanceOf(AccountAdmissionRefusedError);
|
|
602
|
+
expect(asked).toEqual([['invited@gmail.com', 'sso']]);
|
|
603
|
+
expect(vi.mocked(db.insert)).not.toHaveBeenCalled();
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
it('governs a provider that says nothing, or says it did not decide', async () => {
|
|
607
|
+
for (const opts of [undefined, {}, { admittedByProvider: false }]) {
|
|
608
|
+
const { db } = makeFakeDb([[]]);
|
|
609
|
+
await expect(new AuthService(db, config).loginWithSso('someone@gmail.com', 'Someone', opts)).rejects.toThrow(
|
|
610
|
+
/domain is not allowed/i,
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
|
|
576
615
|
it('admits a subdomain of an allowed domain', async () => {
|
|
577
616
|
const { db } = makeFakeDb([[{ ...ROW, email: 'eu@eu.bevel.software', name: 'EU' }]]);
|
|
578
617
|
const out = await new AuthService(db, config).loginWithSso('eu@eu.bevel.software', 'EU');
|
|
@@ -605,3 +644,62 @@ describe('AuthService — the SSO domain allow-list', () => {
|
|
|
605
644
|
expect(out.user.email).toBe('root@gmail.com');
|
|
606
645
|
});
|
|
607
646
|
});
|
|
647
|
+
|
|
648
|
+
describe('AuthService — the account admission port', () => {
|
|
649
|
+
const refusing: IAccountAdmission = {
|
|
650
|
+
canProvision: async () => ({ ok: false, message: 'No seat left on this plan' }),
|
|
651
|
+
};
|
|
652
|
+
const asked: Array<[string, string]> = [];
|
|
653
|
+
const recording: IAccountAdmission = {
|
|
654
|
+
canProvision: async (email, reason) => {
|
|
655
|
+
asked.push([email, reason]);
|
|
656
|
+
return { ok: true };
|
|
657
|
+
},
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
it('refuses a first SSO sign-in the port turns down, and inserts nothing', async () => {
|
|
661
|
+
// The lookup for an existing row answers empty: this is a new address (once per attempt).
|
|
662
|
+
const { db } = makeFakeDb([[], []]);
|
|
663
|
+
const svc = new AuthService(db, makeConfig(), refusing);
|
|
664
|
+
await expect(svc.loginWithSso('new@example.com', 'New')).rejects.toBeInstanceOf(AccountAdmissionRefusedError);
|
|
665
|
+
await expect(svc.loginWithSso('new@example.com', 'New')).rejects.toThrow('No seat left on this plan');
|
|
666
|
+
expect(vi.mocked(db.insert)).not.toHaveBeenCalled();
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
it('refuses an admin creating an account the port turns down, before any hash is stored', async () => {
|
|
670
|
+
const { db } = makeFakeDb([[]]);
|
|
671
|
+
const svc = new AuthService(db, makeConfig(), refusing);
|
|
672
|
+
await expect(svc.createAccount('new@example.com', 'New', 'a-long-enough-password')).rejects.toThrow(
|
|
673
|
+
'No seat left on this plan',
|
|
674
|
+
);
|
|
675
|
+
expect(vi.mocked(db.insert)).not.toHaveBeenCalled();
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
it('never asks about an address that already has an account, so a refusal cannot lock anyone out', async () => {
|
|
679
|
+
asked.length = 0;
|
|
680
|
+
const { db } = makeFakeDb([[{ id: ROW.id }], [ROW]]);
|
|
681
|
+
const svc = new AuthService(db, makeConfig(), refusing);
|
|
682
|
+
const { user } = await svc.loginWithSso('alice@example.com', 'Alice');
|
|
683
|
+
expect(user.email).toBe('alice@example.com');
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
it('names the path a new account arrives by, and admits when the port says so', async () => {
|
|
687
|
+
asked.length = 0;
|
|
688
|
+
const { db } = makeFakeDb([[], [ROW], [], [ROW]]);
|
|
689
|
+
const svc = new AuthService(db, makeConfig(), recording);
|
|
690
|
+
await svc.loginWithSso('alice@example.com', 'Alice');
|
|
691
|
+
await svc.createAccount('alice@example.com', 'Alice', 'a-long-enough-password');
|
|
692
|
+
expect(asked).toEqual([
|
|
693
|
+
['alice@example.com', 'sso'],
|
|
694
|
+
['alice@example.com', 'admin-create'],
|
|
695
|
+
]);
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
it('admits everyone by default, without a lookup', async () => {
|
|
699
|
+
const { db } = makeFakeDb([[ROW]]);
|
|
700
|
+
const svc = new AuthService(db, makeConfig());
|
|
701
|
+
const { user } = await svc.loginWithSso('alice@example.com', 'Alice');
|
|
702
|
+
expect(user.email).toBe('alice@example.com');
|
|
703
|
+
expect(vi.mocked(db.select)).not.toHaveBeenCalled();
|
|
704
|
+
});
|
|
705
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a NEW account may be provisioned on this deployment, asked before
|
|
3
|
+
* the row is inserted. The one seam a host that sells seats needs: core
|
|
4
|
+
* admits everyone, and a cloud overlay answers from its plan without
|
|
5
|
+
* forking any sign-in path.
|
|
6
|
+
*
|
|
7
|
+
* Asked only for an address with no account yet. An existing user's sign-in
|
|
8
|
+
* is never a provisioning, so a refusal can never lock out someone who is
|
|
9
|
+
* already in.
|
|
10
|
+
*/
|
|
11
|
+
export type AccountProvisionReason =
|
|
12
|
+
/** A first sign-in through single sign-on, which provisions on its own. */
|
|
13
|
+
| 'sso'
|
|
14
|
+
/** An admin creating an account from the app. */
|
|
15
|
+
| 'admin-create'
|
|
16
|
+
/** The deployment owner's first password sign-in, which creates their row. */
|
|
17
|
+
| 'bootstrap'
|
|
18
|
+
/** A verified identity from an embedding surface that needs a row without a session. */
|
|
19
|
+
| 'embed';
|
|
20
|
+
|
|
21
|
+
export type AccountAdmissionVerdict = { ok: true } | { ok: false; message: string };
|
|
22
|
+
|
|
23
|
+
export interface IAccountAdmission {
|
|
24
|
+
canProvision(email: string, reason: AccountProvisionReason): Promise<AccountAdmissionVerdict>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Core's default: every account is admitted. */
|
|
28
|
+
export const admitEveryone: IAccountAdmission = {
|
|
29
|
+
canProvision: async () => ({ ok: true }),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The port said no. Carries the port's own words, which the routes surface
|
|
34
|
+
* to the person or admin who asked, and a 403: the identity was fine, the
|
|
35
|
+
* deployment has no place for it.
|
|
36
|
+
*/
|
|
37
|
+
export class AccountAdmissionRefusedError extends Error {
|
|
38
|
+
readonly status = 403;
|
|
39
|
+
|
|
40
|
+
constructor(message: string) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.name = 'AccountAdmissionRefusedError';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -6,6 +6,7 @@ import { printable } from '../../shared/printable.js';
|
|
|
6
6
|
const log = logger('accounts');
|
|
7
7
|
import type { AuthUser } from '@bevel-software/platform-shared';
|
|
8
8
|
import type { AuthService } from './auth.service.js';
|
|
9
|
+
import { AccountAdmissionRefusedError } from './account-admission.js';
|
|
9
10
|
import { erasedAccountId, type IAccountErasureService } from './account-erasure.service.js';
|
|
10
11
|
import type { IAdminAccessService } from '../admin/admin.interface.js';
|
|
11
12
|
import type { UserAccessRemovalService } from '../access/user-access-removal.service.js';
|
|
@@ -64,7 +65,9 @@ export function createAccountRoutes(
|
|
|
64
65
|
res.status(201).json(user);
|
|
65
66
|
} catch (error) {
|
|
66
67
|
const msg = error instanceof Error ? error.message : 'Unknown error';
|
|
67
|
-
|
|
68
|
+
// The deployment has no place for the account (a seat limit, say): the
|
|
69
|
+
// port's own words, as a refusal rather than a malformed request.
|
|
70
|
+
res.status(error instanceof AccountAdmissionRefusedError ? 403 : 400).json({ error: msg });
|
|
68
71
|
}
|
|
69
72
|
});
|
|
70
73
|
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import jwt from 'jsonwebtoken';
|
|
2
2
|
import { eq } from 'drizzle-orm';
|
|
3
3
|
import type { Database } from '../database/connection.js';
|
|
4
|
-
import type { CoreConfig } from '../../core-config.js';
|
|
5
4
|
import { users } from '../database/schema.js';
|
|
6
5
|
import type { AuthUser } from '@bevel-software/platform-shared';
|
|
7
6
|
import { canonicalEmail, hashEmail } from '../../shared/email-identity.js';
|
|
7
|
+
import {
|
|
8
|
+
AccountAdmissionRefusedError,
|
|
9
|
+
admitEveryone,
|
|
10
|
+
type AccountProvisionReason,
|
|
11
|
+
type IAccountAdmission,
|
|
12
|
+
} from './account-admission.js';
|
|
8
13
|
import {
|
|
9
14
|
hashPassword,
|
|
10
15
|
verifyPassword,
|
|
@@ -80,12 +85,53 @@ export interface AuthConfig {
|
|
|
80
85
|
loginPasswordEnabled?: boolean;
|
|
81
86
|
}
|
|
82
87
|
|
|
88
|
+
/** What a sign-in provider tells {@link AuthService.loginWithSso} about the sign-in it hands over. */
|
|
89
|
+
export interface SsoLoginOptions {
|
|
90
|
+
/**
|
|
91
|
+
* The provider has itself decided that this person may enter: they were
|
|
92
|
+
* invited, their address is on a domain the deployment claimed, whatever
|
|
93
|
+
* its rule is. The domain allow-list (`ALLOWED_EMAIL_DOMAINS`) is then
|
|
94
|
+
* not applied.
|
|
95
|
+
*
|
|
96
|
+
* The allow-list exists for a provider that decides nothing: it is what
|
|
97
|
+
* stands between "the issuer knows this person" and "this person has an
|
|
98
|
+
* account here". Applied on top of a provider's own decision it is a
|
|
99
|
+
* second rule the admin set somewhere else, and the person it refuses was
|
|
100
|
+
* let in by the first. Default false, so every provider that does not say
|
|
101
|
+
* otherwise is governed by the allow-list as before.
|
|
102
|
+
*/
|
|
103
|
+
admittedByProvider?: boolean;
|
|
104
|
+
}
|
|
105
|
+
|
|
83
106
|
export class AuthService {
|
|
84
107
|
constructor(
|
|
85
108
|
private readonly db: Database,
|
|
86
109
|
private readonly config: AuthConfig,
|
|
110
|
+
/**
|
|
111
|
+
* Whether a new account may be provisioned — see `account-admission.ts`.
|
|
112
|
+
* Core admits everyone; a host that sells seats answers from its plan.
|
|
113
|
+
*/
|
|
114
|
+
private readonly admission: IAccountAdmission = admitEveryone,
|
|
87
115
|
) {}
|
|
88
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Ask the admission port before an account is CREATED: an address that
|
|
119
|
+
* already has a row is never asked about, so a refusal cannot lock an
|
|
120
|
+
* existing user out. The default port is not asked at all, which also
|
|
121
|
+
* spares the lookup.
|
|
122
|
+
*/
|
|
123
|
+
private async assertAdmitted(normalizedEmail: string, reason: AccountProvisionReason): Promise<void> {
|
|
124
|
+
if (this.admission === admitEveryone) return;
|
|
125
|
+
const [existing] = await this.db
|
|
126
|
+
.select({ id: users.id })
|
|
127
|
+
.from(users)
|
|
128
|
+
.where(eq(users.email, normalizedEmail))
|
|
129
|
+
.limit(1);
|
|
130
|
+
if (existing) return;
|
|
131
|
+
const verdict = await this.admission.canProvision(normalizedEmail, reason);
|
|
132
|
+
if (!verdict.ok) throw new AccountAdmissionRefusedError(verdict.message);
|
|
133
|
+
}
|
|
134
|
+
|
|
89
135
|
/**
|
|
90
136
|
* Password login. Two credential sources, in order:
|
|
91
137
|
*
|
|
@@ -155,6 +201,7 @@ export class AuthService {
|
|
|
155
201
|
throw new Error('Invalid credentials');
|
|
156
202
|
}
|
|
157
203
|
const defaultName = normalizedEmail.split('@')[0] || normalizedEmail;
|
|
204
|
+
await this.assertAdmitted(normalizedEmail, 'bootstrap');
|
|
158
205
|
const admin = await this.upsertUserByEmail(normalizedEmail, defaultName);
|
|
159
206
|
return { token: this.signToken(admin.id, admin.email), user: this.toClientUser(admin) };
|
|
160
207
|
}
|
|
@@ -177,16 +224,26 @@ export class AuthService {
|
|
|
177
224
|
* verified. Same upsert-by-email + JWT path as password login. Email is the
|
|
178
225
|
* idempotency key, so a user who first used password login and later signs
|
|
179
226
|
* in via SSO (same email) keeps the same account/id.
|
|
227
|
+
*
|
|
228
|
+
* WHO MAY ENTER is decided once per sign-in, by whoever is in a position
|
|
229
|
+
* to decide it. For the deployment's own provider that is the domain
|
|
230
|
+
* allow-list: the provider signs in whoever its issuer knows, and nobody
|
|
231
|
+
* has approved the person. A provider that HAS decided (see
|
|
232
|
+
* {@link SsoLoginOptions.admittedByProvider}) is governed by its own rule
|
|
233
|
+
* alone; the allow-list is not laid on top of it. The admission port is
|
|
234
|
+
* asked either way: that is the plan's answer, not a sign-in rule.
|
|
180
235
|
*/
|
|
181
236
|
async loginWithSso(
|
|
182
237
|
email: string,
|
|
183
238
|
name: string,
|
|
239
|
+
opts: SsoLoginOptions = {},
|
|
184
240
|
): Promise<{ token: string; user: AuthUser }> {
|
|
185
241
|
const normalizedEmail = canonicalEmail(email ?? '');
|
|
186
242
|
if (!EMAIL_REGEX.test(normalizedEmail)) {
|
|
187
243
|
throw new Error('Sign-in returned an invalid email');
|
|
188
244
|
}
|
|
189
|
-
this.assertAllowedDomain(normalizedEmail);
|
|
245
|
+
if (!opts.admittedByProvider) this.assertAllowedDomain(normalizedEmail);
|
|
246
|
+
await this.assertAdmitted(normalizedEmail, 'sso');
|
|
190
247
|
const displayName = (name ?? '').trim() || normalizedEmail.split('@')[0] || normalizedEmail;
|
|
191
248
|
const user = await this.upsertUserByEmail(normalizedEmail, displayName);
|
|
192
249
|
return { token: this.signToken(user.id, user.email), user: this.toClientUser(user) };
|
|
@@ -221,6 +278,7 @@ export class AuthService {
|
|
|
221
278
|
throw new Error(ENV_ADMIN_PASSWORD_REFUSAL);
|
|
222
279
|
}
|
|
223
280
|
this.assertPasswordPolicy(password);
|
|
281
|
+
await this.assertAdmitted(normalizedEmail, 'admin-create');
|
|
224
282
|
const suppliedName = (name ?? '').trim();
|
|
225
283
|
const displayName = suppliedName || normalizedEmail.split('@')[0] || normalizedEmail;
|
|
226
284
|
const passwordHash = await hashPassword(password);
|
|
@@ -382,6 +440,7 @@ export class AuthService {
|
|
|
382
440
|
): Promise<{ id: string; email: string; name: string } | null> {
|
|
383
441
|
const normalizedEmail = canonicalEmail(email ?? '');
|
|
384
442
|
if (!EMAIL_REGEX.test(normalizedEmail)) return null;
|
|
443
|
+
await this.assertAdmitted(normalizedEmail, 'embed');
|
|
385
444
|
const displayName = (name ?? '').trim() || normalizedEmail.split('@')[0] || normalizedEmail;
|
|
386
445
|
const user = await this.upsertUserByEmail(normalizedEmail, displayName);
|
|
387
446
|
return { id: user.id, email: user.email, name: user.name };
|
|
@@ -7,6 +7,7 @@ import type { AuthProviderPlugin } from './auth.routes.js';
|
|
|
7
7
|
import { AUTH_COOKIE_MAX_AGE_S } from './auth.routes.js';
|
|
8
8
|
import { AUTH_COOKIE_NAME } from './auth.middleware.js';
|
|
9
9
|
import type { AuthService } from './auth.service.js';
|
|
10
|
+
import { AccountAdmissionRefusedError } from './account-admission.js';
|
|
10
11
|
import { normalizeIssuerUrl } from '../settings/oidc-check.js';
|
|
11
12
|
|
|
12
13
|
// Short-lived CSRF state + PKCE verifier for the OAuth round-trip: set before
|
|
@@ -319,6 +320,14 @@ export class OidcAuthProvider implements AuthProviderPlugin {
|
|
|
319
320
|
});
|
|
320
321
|
res.redirect(`${publicFrontendUrl}/auth/oidc/callback#token=${encodeURIComponent(token)}`);
|
|
321
322
|
} catch (error) {
|
|
323
|
+
// A refused provisioning is not a failed sign-in: the identity was
|
|
324
|
+
// verified, the deployment has no seat for it. Named so the login
|
|
325
|
+
// screen can say so instead of "try again".
|
|
326
|
+
if (error instanceof AccountAdmissionRefusedError) {
|
|
327
|
+
log.warn('OIDC sign-in refused by the account admission port:', { detail: error.message });
|
|
328
|
+
fail('admission');
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
322
331
|
log.error('OIDC callback error:', { err: error });
|
|
323
332
|
fail('auth');
|
|
324
333
|
}
|
|
@@ -399,6 +399,12 @@ export class ConnectionProbeService implements IConnectionProbeService {
|
|
|
399
399
|
constructor(
|
|
400
400
|
private readonly toolManualService: IToolManualService,
|
|
401
401
|
private readonly secretsVault: ISecretsVaultService,
|
|
402
|
+
/**
|
|
403
|
+
* The scope this knowledge base's vault was registered under with the
|
|
404
|
+
* UTCP variable loader, so a probe's client resolves the caller's secrets
|
|
405
|
+
* from it — see `secrets-variable-loader.ts`. Default: the single one.
|
|
406
|
+
*/
|
|
407
|
+
private readonly secretsScope?: string,
|
|
402
408
|
) {}
|
|
403
409
|
|
|
404
410
|
async probe(userId: string, userEmail: string, slug: string): Promise<ProbeVerdict | null> {
|
|
@@ -556,7 +562,7 @@ export class ConnectionProbeService implements IConnectionProbeService {
|
|
|
556
562
|
// loader, which is exactly the credential under test.
|
|
557
563
|
const config = new UtcpClientConfigSerializer().validateDict({
|
|
558
564
|
variables: {},
|
|
559
|
-
load_variables_from: [bevelSecretsLoaderConfig(userId)],
|
|
565
|
+
load_variables_from: [bevelSecretsLoaderConfig(userId, this.secretsScope)],
|
|
560
566
|
});
|
|
561
567
|
// No late-settle owner: a client that arrives after the deadline has no
|
|
562
568
|
// manual registered and therefore no session, and neither
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { AdvisoryLock, withAdvisoryLock } from '../advisory-lock.js';
|
|
2
|
+
import { AdvisoryLock, advisoryLockKey, withAdvisoryLock } from '../advisory-lock.js';
|
|
3
3
|
import type { Database } from '../connection.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -145,3 +145,25 @@ describe('withAdvisoryLock', () => {
|
|
|
145
145
|
expect(clients[0].releasedWith).toBeInstanceOf(Error);
|
|
146
146
|
});
|
|
147
147
|
});
|
|
148
|
+
|
|
149
|
+
describe('advisoryLockKey', () => {
|
|
150
|
+
it('keeps the ids a single-tenant deployment always used when no tenant is named', () => {
|
|
151
|
+
expect(advisoryLockKey(AdvisoryLock.Migrations)).toBe(AdvisoryLock.Migrations);
|
|
152
|
+
expect(advisoryLockKey(AdvisoryLock.CommitWorker, '')).toBe(AdvisoryLock.CommitWorker);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('gives each tenant its own key for each lock, stable across calls and inside int4', () => {
|
|
156
|
+
const a = advisoryLockKey(AdvisoryLock.CommitWorker, 't_acme');
|
|
157
|
+
const b = advisoryLockKey(AdvisoryLock.CommitWorker, 't_globex');
|
|
158
|
+
expect(a).toBe(advisoryLockKey(AdvisoryLock.CommitWorker, 't_acme'));
|
|
159
|
+
expect(a).not.toBe(b);
|
|
160
|
+
expect(a).not.toBe(AdvisoryLock.CommitWorker);
|
|
161
|
+
// The two locks of one tenant stay two locks.
|
|
162
|
+
expect(advisoryLockKey(AdvisoryLock.Migrations, 't_acme')).not.toBe(a);
|
|
163
|
+
for (const key of [a, b]) {
|
|
164
|
+
expect(Number.isInteger(key)).toBe(true);
|
|
165
|
+
expect(key).toBeGreaterThanOrEqual(-(2 ** 31));
|
|
166
|
+
expect(key).toBeLessThanOrEqual(2 ** 31 - 1);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_DB_SCHEMA,
|
|
4
|
+
assertSchemaName,
|
|
5
|
+
assertSearchPath,
|
|
6
|
+
closeDb,
|
|
7
|
+
createDb,
|
|
8
|
+
dbSchemaOf,
|
|
9
|
+
getDb,
|
|
10
|
+
type Database,
|
|
11
|
+
} from '../connection.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Pools here are never connected: `pg.Pool` opens nothing until a query is
|
|
15
|
+
* asked of it, so what a handle was built with — the schema its connections
|
|
16
|
+
* will search, whether two asks share one pool — is observable without a
|
|
17
|
+
* database, and that is what these cases pin.
|
|
18
|
+
*/
|
|
19
|
+
const URL = 'postgresql://u:p@127.0.0.1:1/never';
|
|
20
|
+
|
|
21
|
+
const searchPath = (db: Database): string | undefined =>
|
|
22
|
+
(db.$client.options as { options?: string }).options;
|
|
23
|
+
|
|
24
|
+
const opened: Database[] = [];
|
|
25
|
+
afterEach(async () => {
|
|
26
|
+
await Promise.all(opened.splice(0).map((db) => closeDb(db)));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('schema per knowledge base', () => {
|
|
30
|
+
it('searches the schema it was built for, and leaves the default schema to the server', () => {
|
|
31
|
+
const acme = createDb(URL, { schema: 'acme' });
|
|
32
|
+
const plain = createDb(URL);
|
|
33
|
+
opened.push(acme, plain);
|
|
34
|
+
expect(searchPath(acme)).toBe('-c search_path=acme');
|
|
35
|
+
expect(dbSchemaOf(acme)).toBe('acme');
|
|
36
|
+
// Nothing set: an operator's own `search_path` stays in force.
|
|
37
|
+
expect(searchPath(plain)).toBeUndefined();
|
|
38
|
+
expect(dbSchemaOf(plain)).toBe(DEFAULT_DB_SCHEMA);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('refuses a schema name that is not a plain lowercase identifier', () => {
|
|
42
|
+
for (const bad of ['Acme', 'a-b', 'a b', '1abc', 'public; drop schema x', '', 'a'.repeat(64)]) {
|
|
43
|
+
expect(() => assertSchemaName(bad), bad).toThrow(/schema name/);
|
|
44
|
+
expect(() => createDb(URL, { schema: bad }), bad).toThrow(/schema name/);
|
|
45
|
+
}
|
|
46
|
+
expect(assertSchemaName('t_acme_2')).toBe('t_acme_2');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('refuses the names Postgres and drizzle already own', () => {
|
|
50
|
+
for (const reserved of ['pg_catalog', 'pg_toast', 'pg_x', 'information_schema', 'drizzle']) {
|
|
51
|
+
expect(() => assertSchemaName(reserved), reserved).toThrow(/reserved/);
|
|
52
|
+
}
|
|
53
|
+
// The default schema is a valid name here: a single-tenant deployment
|
|
54
|
+
// lives on it. A tenant source refuses it for a tenant, not this guard.
|
|
55
|
+
expect(assertSchemaName(DEFAULT_DB_SCHEMA)).toBe(DEFAULT_DB_SCHEMA);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('assertSearchPath', () => {
|
|
60
|
+
const answering = (schema: string | null) => ({
|
|
61
|
+
execute: async () => ({ rows: [{ schema }] }) as never,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('passes when the server searches the configured schema first', async () => {
|
|
65
|
+
await expect(assertSearchPath(answering('t_acme'), 't_acme')).resolves.toBeUndefined();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('refuses a connection whose schema a pooler dropped, naming both schemas and the cause', async () => {
|
|
69
|
+
// What a transaction-mode pooler leaves behind: the startup parameter
|
|
70
|
+
// never reached the server, so `public` is searched first.
|
|
71
|
+
await expect(assertSearchPath(answering('public'), 't_acme')).rejects.toThrow(
|
|
72
|
+
/search "public" first, not the configured schema "t_acme".*transaction-mode pooler/,
|
|
73
|
+
);
|
|
74
|
+
await expect(assertSearchPath(answering(null), 't_acme')).rejects.toThrow(/\(none\)/);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('getDb', () => {
|
|
79
|
+
it('shares one pool per (url, schema) and keeps different schemas apart', () => {
|
|
80
|
+
const a1 = getDb(URL, { schema: 'acme' });
|
|
81
|
+
const a2 = getDb(URL, { schema: 'acme' });
|
|
82
|
+
const b = getDb(URL, { schema: 'globex' });
|
|
83
|
+
const plain = getDb(URL);
|
|
84
|
+
opened.push(a1, b, plain);
|
|
85
|
+
expect(a2).toBe(a1);
|
|
86
|
+
expect(b).not.toBe(a1);
|
|
87
|
+
expect(plain).not.toBe(a1);
|
|
88
|
+
expect(getDb(URL, { schema: DEFAULT_DB_SCHEMA })).toBe(plain);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('opens a fresh pool after the cached one was closed', async () => {
|
|
92
|
+
const first = getDb(URL, { schema: 'closing' });
|
|
93
|
+
await closeDb(first);
|
|
94
|
+
const second = getDb(URL, { schema: 'closing' });
|
|
95
|
+
opened.push(second);
|
|
96
|
+
expect(second).not.toBe(first);
|
|
97
|
+
});
|
|
98
|
+
});
|