@bevel-software/platform-core-backend 0.26.0 → 0.28.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/agent-guide/access-control.md +17 -0
- package/agent-guide/conventions.md +8 -0
- package/agent-guide/html-views.md +83 -0
- package/agent-guide/new-knowledge-base.md +24 -0
- package/agent-guide/tool-manuals.md +10 -0
- package/dist/assets.d.ts +19 -0
- package/dist/assets.d.ts.map +1 -1
- package/dist/assets.js +26 -2
- package/dist/assets.js.map +1 -1
- package/dist/core/catalog-revision.d.ts +19 -1
- package/dist/core/catalog-revision.d.ts.map +1 -1
- package/dist/core/catalog-revision.js +14 -3
- package/dist/core/catalog-revision.js.map +1 -1
- package/dist/core/create-core-server.d.ts +5 -0
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +56 -5
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +33 -0
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +105 -8
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +16 -1
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +18 -1
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/core-config.d.ts +21 -0
- package/dist/core-config.d.ts.map +1 -1
- package/dist/core-config.js +22 -1
- package/dist/core-config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +17 -0
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +2 -0
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +31 -0
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts +4 -1
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +66 -7
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/directory-sync-bot.js +1 -1
- package/dist/modules/access/directory-sync-bot.js.map +1 -1
- package/dist/modules/access/roles-admin.service.d.ts +35 -6
- package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
- package/dist/modules/access/roles-admin.service.js +36 -9
- package/dist/modules/access/roles-admin.service.js.map +1 -1
- package/dist/modules/access/user-access-removal.service.js +1 -1
- package/dist/modules/access/user-access-removal.service.js.map +1 -1
- package/dist/modules/agent-guide/agent-guide.d.ts +6 -0
- package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.js +8 -0
- package/dist/modules/agent-guide/agent-guide.js.map +1 -1
- package/dist/modules/agent-guide/index.d.ts +1 -1
- package/dist/modules/agent-guide/index.d.ts.map +1 -1
- package/dist/modules/agent-guide/index.js +1 -1
- package/dist/modules/agent-guide/index.js.map +1 -1
- package/dist/modules/agent-instructions/compose.d.ts +5 -0
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
- package/dist/modules/agent-instructions/compose.js +19 -2
- package/dist/modules/agent-instructions/compose.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.js +19 -19
- package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
- package/dist/modules/audit/agent-audit.service.d.ts +21 -2
- package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
- package/dist/modules/audit/agent-audit.service.js +43 -2
- package/dist/modules/audit/agent-audit.service.js.map +1 -1
- package/dist/modules/audit/agent-connection.routes.d.ts +34 -0
- package/dist/modules/audit/agent-connection.routes.d.ts.map +1 -0
- package/dist/modules/audit/agent-connection.routes.js +38 -0
- package/dist/modules/audit/agent-connection.routes.js.map +1 -0
- package/dist/modules/audit/audit.contract.d.ts +24 -0
- package/dist/modules/audit/audit.contract.d.ts.map +1 -1
- package/dist/modules/audit/audit.contract.js.map +1 -1
- package/dist/modules/audit/index.d.ts +1 -0
- package/dist/modules/audit/index.d.ts.map +1 -1
- package/dist/modules/audit/index.js +1 -0
- package/dist/modules/audit/index.js.map +1 -1
- package/dist/modules/auth/account-hooks.d.ts +46 -0
- package/dist/modules/auth/account-hooks.d.ts.map +1 -0
- package/dist/modules/auth/account-hooks.js +25 -0
- package/dist/modules/auth/account-hooks.js.map +1 -0
- package/dist/modules/auth/account-switch-guard.d.ts +15 -0
- package/dist/modules/auth/account-switch-guard.d.ts.map +1 -0
- package/dist/modules/auth/account-switch-guard.js +2 -0
- package/dist/modules/auth/account-switch-guard.js.map +1 -0
- package/dist/modules/auth/account-switch-lock.d.ts +34 -0
- package/dist/modules/auth/account-switch-lock.d.ts.map +1 -0
- package/dist/modules/auth/account-switch-lock.js +48 -0
- package/dist/modules/auth/account-switch-lock.js.map +1 -0
- package/dist/modules/auth/account.routes.d.ts +4 -1
- package/dist/modules/auth/account.routes.d.ts.map +1 -1
- package/dist/modules/auth/account.routes.js +110 -13
- package/dist/modules/auth/account.routes.js.map +1 -1
- package/dist/modules/auth/auth.middleware.d.ts +7 -0
- package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
- package/dist/modules/auth/auth.middleware.js +1 -1
- package/dist/modules/auth/auth.middleware.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +101 -6
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +246 -51
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/auth/password-hash.d.ts +5 -2
- package/dist/modules/auth/password-hash.d.ts.map +1 -1
- package/dist/modules/auth/password-hash.js +5 -2
- package/dist/modules/auth/password-hash.js.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.js +11 -3
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +96 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +32 -3
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/database/pg-error.d.ts +9 -0
- package/dist/modules/database/pg-error.d.ts.map +1 -0
- package/dist/modules/database/pg-error.js +17 -0
- package/dist/modules/database/pg-error.js.map +1 -0
- package/dist/modules/embed/account-link.service.d.ts +42 -0
- package/dist/modules/embed/account-link.service.d.ts.map +1 -0
- package/dist/modules/embed/account-link.service.js +50 -0
- package/dist/modules/embed/account-link.service.js.map +1 -0
- package/dist/modules/embed/embed-link.d.ts +58 -0
- package/dist/modules/embed/embed-link.d.ts.map +1 -0
- package/dist/modules/embed/embed-link.js +160 -0
- package/dist/modules/embed/embed-link.js.map +1 -0
- package/dist/modules/embed/embed.errors.d.ts +30 -0
- package/dist/modules/embed/embed.errors.d.ts.map +1 -0
- package/dist/modules/embed/embed.errors.js +42 -0
- package/dist/modules/embed/embed.errors.js.map +1 -0
- package/dist/modules/embed/embed.interface.d.ts +204 -0
- package/dist/modules/embed/embed.interface.d.ts.map +1 -0
- package/dist/modules/embed/embed.interface.js +2 -0
- package/dist/modules/embed/embed.interface.js.map +1 -0
- package/dist/modules/embed/embed.routes.d.ts +59 -0
- package/dist/modules/embed/embed.routes.d.ts.map +1 -0
- package/dist/modules/embed/embed.routes.js +343 -0
- package/dist/modules/embed/embed.routes.js.map +1 -0
- package/dist/modules/embed/embed.service.d.ts +169 -0
- package/dist/modules/embed/embed.service.d.ts.map +1 -0
- package/dist/modules/embed/embed.service.js +627 -0
- package/dist/modules/embed/embed.service.js.map +1 -0
- package/dist/modules/embed/embed.tools.d.ts +51 -0
- package/dist/modules/embed/embed.tools.d.ts.map +1 -0
- package/dist/modules/embed/embed.tools.js +170 -0
- package/dist/modules/embed/embed.tools.js.map +1 -0
- package/dist/modules/embed/mcp-app.d.ts +64 -0
- package/dist/modules/embed/mcp-app.d.ts.map +1 -0
- package/dist/modules/embed/mcp-app.js +147 -0
- package/dist/modules/embed/mcp-app.js.map +1 -0
- package/dist/modules/embed/mcp-app.routes.d.ts +21 -0
- package/dist/modules/embed/mcp-app.routes.d.ts.map +1 -0
- package/dist/modules/embed/mcp-app.routes.js +36 -0
- package/dist/modules/embed/mcp-app.routes.js.map +1 -0
- package/dist/modules/github-app/github-app.connection.d.ts +15 -0
- package/dist/modules/github-app/github-app.connection.d.ts.map +1 -1
- package/dist/modules/github-app/github-app.connection.js +34 -3
- package/dist/modules/github-app/github-app.connection.js.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.d.ts +65 -0
- package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.js +300 -0
- package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
- package/dist/modules/kb-sync/kb-sync.interface.d.ts +5 -0
- package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.service.js +6 -0
- package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +2 -1
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +28 -1
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +64 -8
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -0
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +32 -4
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/onboarding/onboarding.contract.d.ts +80 -0
- package/dist/modules/onboarding/onboarding.contract.d.ts.map +1 -0
- package/dist/modules/onboarding/onboarding.contract.js +7 -0
- package/dist/modules/onboarding/onboarding.contract.js.map +1 -0
- package/dist/modules/onboarding/onboarding.routes.d.ts +18 -0
- package/dist/modules/onboarding/onboarding.routes.d.ts.map +1 -0
- package/dist/modules/onboarding/onboarding.routes.js +81 -0
- package/dist/modules/onboarding/onboarding.routes.js.map +1 -0
- package/dist/modules/onboarding/starter-pack.service.d.ts +213 -0
- package/dist/modules/onboarding/starter-pack.service.d.ts.map +1 -0
- package/dist/modules/onboarding/starter-pack.service.js +492 -0
- package/dist/modules/onboarding/starter-pack.service.js.map +1 -0
- package/dist/modules/onboarding/starter-packs.d.ts +78 -0
- package/dist/modules/onboarding/starter-packs.d.ts.map +1 -0
- package/dist/modules/onboarding/starter-packs.js +156 -0
- package/dist/modules/onboarding/starter-packs.js.map +1 -0
- 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/plugin-provision.service.d.ts +19 -0
- package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.js +18 -1
- package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +0 -36
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +31 -0
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.d.ts +34 -0
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +134 -1
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/settings/repository-source.d.ts +1 -0
- package/dist/modules/settings/repository-source.d.ts.map +1 -1
- package/dist/modules/settings/repository-source.js +3 -3
- package/dist/modules/settings/repository-source.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +50 -32
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-auth/connection-key-rejection.d.ts +10 -1
- package/dist/modules/tool-auth/connection-key-rejection.d.ts.map +1 -1
- package/dist/modules/tool-auth/connection-key-rejection.js +11 -3
- package/dist/modules/tool-auth/connection-key-rejection.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.d.ts +8 -0
- package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.d.ts +24 -2
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +59 -2
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +9 -4
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/index.d.ts +28 -1
- package/dist/modules/tool-helpers/index.d.ts.map +1 -1
- package/dist/modules/tool-helpers/index.js +28 -1
- package/dist/modules/tool-helpers/index.js.map +1 -1
- package/dist/modules/tool-helpers/route-argument-check.d.ts +10 -0
- package/dist/modules/tool-helpers/route-argument-check.d.ts.map +1 -0
- package/dist/modules/tool-helpers/route-argument-check.js +132 -0
- package/dist/modules/tool-helpers/route-argument-check.js.map +1 -0
- package/dist/modules/tool-helpers/route-tool-schemas.d.ts +89 -0
- package/dist/modules/tool-helpers/route-tool-schemas.d.ts.map +1 -0
- package/dist/modules/tool-helpers/route-tool-schemas.js +113 -0
- package/dist/modules/tool-helpers/route-tool-schemas.js.map +1 -0
- package/dist/modules/tool-helpers/tool-def.d.ts +71 -0
- package/dist/modules/tool-helpers/tool-def.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-def.js +70 -6
- package/dist/modules/tool-helpers/tool-def.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts +46 -6
- package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.js +157 -8
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts +9 -0
- 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-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +14 -5
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/tool-registry/description-length.d.ts +6 -2
- package/dist/modules/tool-registry/description-length.d.ts.map +1 -1
- package/dist/modules/tool-registry/description-length.js +27 -5
- package/dist/modules/tool-registry/description-length.js.map +1 -1
- package/dist/modules/tool-registry/guide-first.d.ts +4 -0
- package/dist/modules/tool-registry/guide-first.d.ts.map +1 -1
- package/dist/modules/tool-registry/guide-first.js +9 -1
- package/dist/modules/tool-registry/guide-first.js.map +1 -1
- package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts +53 -0
- package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/change-request-read.tools.js +97 -68
- package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts +8 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +124 -37
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/file-lock.service.d.ts +29 -5
- package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
- package/dist/modules/workflow/file-lock.service.js +62 -5
- 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 +172 -36
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/node-git-runner.d.ts +15 -0
- package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -1
- package/dist/modules/workflow/git/node-git-runner.js +78 -6
- package/dist/modules/workflow/git/node-git-runner.js.map +1 -1
- package/dist/modules/workflow/pending-commits.service.d.ts +15 -0
- package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.service.js +25 -1
- package/dist/modules/workflow/pending-commits.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.d.ts +6 -1
- package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.js +23 -6
- package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
- package/dist/modules/workflow/recovery-bot.js +1 -1
- package/dist/modules/workflow/recovery-bot.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 +2 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
- package/dist/modules/workflow/sanitize-error.d.ts +16 -0
- package/dist/modules/workflow/sanitize-error.d.ts.map +1 -1
- package/dist/modules/workflow/sanitize-error.js +45 -0
- package/dist/modules/workflow/sanitize-error.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +183 -15
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +807 -107
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/agent-download.builder.d.ts +105 -0
- package/dist/modules/workspace/agent-download.builder.d.ts.map +1 -0
- package/dist/modules/workspace/agent-download.builder.js +238 -0
- package/dist/modules/workspace/agent-download.builder.js.map +1 -0
- package/dist/modules/workspace/agent-download.routes.d.ts +77 -0
- package/dist/modules/workspace/agent-download.routes.d.ts.map +1 -0
- package/dist/modules/workspace/agent-download.routes.js +156 -0
- package/dist/modules/workspace/agent-download.routes.js.map +1 -0
- package/dist/modules/workspace/agent-download.store.d.ts +225 -0
- package/dist/modules/workspace/agent-download.store.d.ts.map +1 -0
- package/dist/modules/workspace/agent-download.store.js +423 -0
- package/dist/modules/workspace/agent-download.store.js.map +1 -0
- package/dist/modules/workspace/agent-upload.store.d.ts +10 -1
- package/dist/modules/workspace/agent-upload.store.d.ts.map +1 -1
- package/dist/modules/workspace/agent-upload.store.js +16 -6
- package/dist/modules/workspace/agent-upload.store.js.map +1 -1
- package/dist/modules/workspace/file-readers/raw-mime.d.ts +6 -0
- package/dist/modules/workspace/file-readers/raw-mime.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/raw-mime.js +38 -0
- package/dist/modules/workspace/file-readers/raw-mime.js.map +1 -0
- package/dist/modules/workspace/file-readers/text-reader.d.ts +5 -5
- package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/text-reader.js +5 -8
- package/dist/modules/workspace/file-readers/text-reader.js.map +1 -1
- package/dist/modules/workspace/first-run.d.ts +111 -0
- package/dist/modules/workspace/first-run.d.ts.map +1 -0
- package/dist/modules/workspace/first-run.js +181 -0
- package/dist/modules/workspace/first-run.js.map +1 -0
- package/dist/modules/workspace/move-links.d.ts +92 -0
- package/dist/modules/workspace/move-links.d.ts.map +1 -0
- package/dist/modules/workspace/move-links.js +173 -0
- package/dist/modules/workspace/move-links.js.map +1 -0
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js +3 -13
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +63 -22
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +75 -2
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +197 -24
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +59 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +443 -65
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/modules/write-access/write-access.d.ts.map +1 -1
- package/dist/modules/write-access/write-access.js +9 -0
- package/dist/modules/write-access/write-access.js.map +1 -1
- package/dist/shared/domain-errors.d.ts +73 -1
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +86 -12
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/git.contract.d.ts +10 -1
- package/dist/shared/git.contract.d.ts.map +1 -1
- package/dist/shared/git.contract.js.map +1 -1
- package/dist/shared/utf8-text.d.ts +15 -0
- package/dist/shared/utf8-text.d.ts.map +1 -0
- package/dist/shared/utf8-text.js +20 -0
- package/dist/shared/utf8-text.js.map +1 -0
- package/dist/tenancy/static-tenant-source.d.ts +2 -1
- package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
- package/dist/tenancy/static-tenant-source.js +9 -2
- package/dist/tenancy/static-tenant-source.js.map +1 -1
- package/mcp-app/page.html +349 -0
- package/migrations/0017_atlassian_account_links.sql +37 -0
- package/migrations/meta/0017_snapshot.json +2396 -0
- package/migrations/meta/_journal.json +8 -1
- package/package.json +5 -3
- package/src/__tests__/packaged-assets-ship.test.ts +16 -4
- package/src/__tests__/pg-scratch.ts +48 -0
- package/src/assets.ts +28 -2
- package/src/core/__tests__/catalog-revision.test.ts +40 -0
- package/src/core/__tests__/gated-boot.test.ts +1 -0
- package/src/core/__tests__/lifecycle.test.ts +14 -0
- package/src/core/catalog-revision.ts +15 -2
- package/src/core/create-core-server.ts +69 -3
- package/src/core/create-core-services.ts +151 -6
- package/src/core/lifecycle.ts +28 -2
- package/src/core-config.ts +24 -1
- package/src/index.ts +2 -2
- package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +1 -0
- package/src/modules/access/__tests__/access-control.service.test.ts +19 -0
- package/src/modules/access/__tests__/access.routes.admins.test.ts +203 -0
- package/src/modules/access/__tests__/access.routes.batch.test.ts +25 -5
- package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +1 -0
- package/src/modules/access/__tests__/access.routes.group-grant.test.ts +1 -1
- package/src/modules/access/__tests__/access.routes.overrides.test.ts +1 -1
- package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +1 -0
- package/src/modules/access/__tests__/access.routes.prospective.test.ts +1 -0
- package/src/modules/access/__tests__/access.routes.revoke.test.ts +1 -1
- package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +1 -0
- package/src/modules/access/__tests__/roles-admin.security.test.ts +1 -1
- package/src/modules/access/__tests__/roles-admin.service.test.ts +15 -2
- package/src/modules/access/__tests__/roles.routes.test.ts +132 -7
- package/src/modules/access/__tests__/user-access-removal.service.test.ts +2 -2
- package/src/modules/access/__tests__/users-db-double.ts +12 -3
- package/src/modules/access/access-control.interface.ts +23 -0
- package/src/modules/access/access-control.service.ts +35 -0
- package/src/modules/access/access.routes.ts +67 -6
- package/src/modules/access/directory-sync-bot.ts +1 -1
- package/src/modules/access/roles-admin.service.ts +51 -11
- package/src/modules/access/user-access-removal.service.ts +1 -1
- package/src/modules/agent-guide/__tests__/agent-guide.test.ts +58 -0
- package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +19 -1
- package/src/modules/agent-guide/agent-guide.ts +9 -0
- package/src/modules/agent-guide/index.ts +1 -0
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +14 -0
- package/src/modules/agent-instructions/compose.ts +17 -2
- package/src/modules/agent-instructions/shared-file-rules.ts +19 -19
- package/src/modules/audit/__tests__/agent-audit.service.test.ts +64 -4
- package/src/modules/audit/__tests__/agent-connection.routes.test.ts +102 -0
- package/src/modules/audit/agent-audit.service.ts +58 -3
- package/src/modules/audit/agent-connection.routes.ts +56 -0
- package/src/modules/audit/audit.contract.ts +26 -0
- package/src/modules/audit/index.ts +1 -0
- package/src/modules/auth/__tests__/account-deactivation.test.ts +145 -16
- package/src/modules/auth/__tests__/account-hooks.test.ts +57 -0
- package/src/modules/auth/__tests__/account-switch-lock.pg.test.ts +78 -0
- package/src/modules/auth/__tests__/account-switch-lock.test.ts +43 -0
- package/src/modules/auth/__tests__/account.routes.test.ts +387 -9
- package/src/modules/auth/__tests__/auth.service.created.pg.test.ts +69 -0
- package/src/modules/auth/__tests__/auth.service.test.ts +159 -1
- package/src/modules/auth/account-hooks.ts +69 -0
- package/src/modules/auth/account-switch-guard.ts +14 -0
- package/src/modules/auth/account-switch-lock.ts +58 -0
- package/src/modules/auth/account.routes.ts +122 -13
- package/src/modules/auth/auth.middleware.ts +1 -1
- package/src/modules/auth/auth.service.ts +268 -52
- package/src/modules/auth/password-hash.ts +5 -2
- package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +18 -2
- package/src/modules/code-mode/code-mode.tool.ts +13 -2
- package/src/modules/database/__tests__/pg-error.test.ts +21 -0
- package/src/modules/database/__tests__/pii-backfill.pg.test.ts +6 -27
- package/src/modules/database/core-schema.ts +33 -3
- package/src/modules/database/pg-error.ts +15 -0
- package/src/modules/embed/__tests__/account-links.migration.test.ts +98 -0
- package/src/modules/embed/__tests__/embed-link.test.ts +122 -0
- package/src/modules/embed/__tests__/embed.routes.test.ts +362 -0
- package/src/modules/embed/__tests__/embed.service.lock.test.ts +193 -0
- package/src/modules/embed/__tests__/embed.service.test.ts +805 -0
- package/src/modules/embed/__tests__/embed.tools.test.ts +304 -0
- package/src/modules/embed/__tests__/mcp-app.test.ts +207 -0
- package/src/modules/embed/account-link.service.ts +76 -0
- package/src/modules/embed/embed-link.ts +180 -0
- package/src/modules/embed/embed.errors.ts +43 -0
- package/src/modules/embed/embed.interface.ts +203 -0
- package/src/modules/embed/embed.routes.ts +363 -0
- package/src/modules/embed/embed.service.ts +705 -0
- package/src/modules/embed/embed.tools.ts +206 -0
- package/src/modules/embed/mcp-app.routes.ts +41 -0
- package/src/modules/embed/mcp-app.ts +173 -0
- package/src/modules/github-app/__tests__/github-app.test.ts +76 -0
- package/src/modules/github-app/github-app.connection.ts +38 -4
- package/src/modules/kb-fs/__tests__/move-with-edits.test.ts +304 -0
- package/src/modules/kb-fs/locking-filesystem.ts +287 -0
- package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +35 -0
- package/src/modules/kb-sync/kb-sync.interface.ts +2 -0
- package/src/modules/kb-sync/kb-sync.service.ts +6 -0
- package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +68 -11
- package/src/modules/mcp/__tests__/mcp-proxy-helpers.test.ts +37 -2
- package/src/modules/mcp/__tests__/mcp.app.e2e.test.ts +200 -0
- package/src/modules/mcp/__tests__/mcp.e2e.test.ts +3 -1
- package/src/modules/mcp/__tests__/mcp.service.test.ts +23 -13
- package/src/modules/mcp/__tests__/tool-call-interface.e2e.test.ts +499 -0
- package/src/modules/mcp/mcp-auth.middleware.ts +2 -1
- package/src/modules/mcp/mcp.service.ts +81 -12
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +42 -4
- package/src/modules/onboarding/__tests__/onboarding.routes.test.ts +163 -0
- package/src/modules/onboarding/__tests__/starter-pack.service.test.ts +635 -0
- package/src/modules/onboarding/__tests__/starter-packs.test.ts +169 -0
- package/src/modules/onboarding/onboarding.contract.ts +85 -0
- package/src/modules/onboarding/onboarding.routes.ts +85 -0
- package/src/modules/onboarding/starter-pack.service.ts +580 -0
- package/src/modules/onboarding/starter-packs.ts +182 -0
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +25 -2
- package/src/modules/plugins/index.ts +1 -0
- package/src/modules/plugins/plugin-provision.service.ts +22 -1
- package/src/modules/plugins/plugins.routes.ts +29 -0
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +150 -1
- package/src/modules/settings/__tests__/repository-source.test.ts +9 -0
- package/src/modules/settings/deployment-settings.service.ts +131 -1
- package/src/modules/settings/repository-source.ts +4 -4
- package/src/modules/skills/__tests__/skills.tools.description.test.ts +5 -1
- package/src/modules/skills/skills.tools.ts +55 -35
- package/src/modules/tool-auth/__tests__/connection-key-rejection.test.ts +32 -0
- package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +65 -0
- package/src/modules/tool-auth/connection-key-rejection.ts +13 -3
- package/src/modules/tool-auth/external-api-key.interface.ts +9 -0
- package/src/modules/tool-auth/external-api-key.service.ts +61 -4
- package/src/modules/tool-auth/tool-auth.middleware.ts +13 -4
- package/src/modules/tool-helpers/__tests__/branch-declaration.test.ts +502 -0
- package/src/modules/tool-helpers/__tests__/route-argument-check.test.ts +337 -0
- package/src/modules/tool-helpers/index.ts +34 -1
- package/src/modules/tool-helpers/route-argument-check.ts +149 -0
- package/src/modules/tool-helpers/route-tool-schemas.ts +171 -0
- package/src/modules/tool-helpers/tool-def.ts +132 -7
- package/src/modules/tool-helpers/tool-handler.ts +196 -8
- package/src/modules/tool-helpers/tool.contract.ts +9 -0
- package/src/modules/tool-manuals/tool-manuals.tools.ts +15 -5
- package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +14 -1
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +23 -10
- package/src/modules/tool-registry/description-length.ts +34 -7
- package/src/modules/tool-registry/guide-first.ts +8 -1
- package/src/modules/workflow/__tests__/file-lock.service.deletion-gate.test.ts +104 -0
- package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +66 -0
- package/src/modules/workflow/__tests__/pending-commits.worker.test.ts +31 -2
- package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +110 -6
- package/src/modules/workflow/__tests__/sanitize-error.test.ts +89 -1
- package/src/modules/workflow/__tests__/workflow.routes.push-refused.test.ts +202 -0
- package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +6 -6
- package/src/modules/workflow/__tests__/workflow.service.delete-branch.test.ts +1026 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +468 -10
- package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +4 -0
- package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +5 -4
- package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +54 -2
- package/src/modules/workflow/agent-tools/__tests__/change-request-read.tools.test.ts +41 -5
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +127 -2
- package/src/modules/workflow/agent-tools/change-request-read.tools.ts +157 -122
- package/src/modules/workflow/agent-tools/workflow.tools.ts +129 -35
- package/src/modules/workflow/file-lock.service.ts +67 -5
- package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +164 -0
- package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +48 -1
- package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +231 -2
- package/src/modules/workflow/git/git.service.ts +176 -38
- package/src/modules/workflow/git/node-git-runner.ts +82 -7
- package/src/modules/workflow/pending-commits.service.ts +29 -1
- package/src/modules/workflow/pending-commits.worker.ts +24 -6
- package/src/modules/workflow/recovery-bot.ts +1 -1
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +2 -1
- package/src/modules/workflow/sanitize-error.ts +50 -0
- package/src/modules/workflow/workflow.service.ts +881 -113
- package/src/modules/workspace/__tests__/agent-downloads.test.ts +823 -0
- package/src/modules/workspace/__tests__/first-run.test.ts +234 -0
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +5 -2
- package/src/modules/workspace/__tests__/md-links.test.ts +353 -0
- package/src/modules/workspace/__tests__/move-links.test.ts +261 -0
- package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +219 -0
- package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +3 -0
- package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +102 -0
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +52 -5
- package/src/modules/workspace/__tests__/workspace.routes.folder-zip-access.test.ts +190 -0
- package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +8 -4
- package/src/modules/workspace/__tests__/workspace.service.test.ts +50 -8
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +44 -1
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +409 -12
- package/src/modules/workspace/agent-download.builder.ts +308 -0
- package/src/modules/workspace/agent-download.routes.ts +183 -0
- package/src/modules/workspace/agent-download.store.ts +533 -0
- package/src/modules/workspace/agent-upload.store.ts +23 -7
- package/src/modules/workspace/file-readers/raw-mime.ts +40 -0
- package/src/modules/workspace/file-readers/text-reader.ts +5 -8
- package/src/modules/workspace/first-run.ts +222 -0
- package/src/modules/workspace/move-links.ts +237 -0
- package/src/modules/workspace/startup/steps/seed-tree.ts +3 -12
- package/src/modules/workspace/workspace.routes.ts +64 -22
- package/src/modules/workspace/workspace.service.ts +202 -25
- package/src/modules/workspace/workspace.tools.ts +492 -63
- package/src/modules/write-access/__tests__/write-access.test.ts +13 -0
- package/src/modules/write-access/write-access.ts +9 -0
- package/src/shared/domain-errors.ts +108 -11
- package/src/shared/git.contract.ts +8 -1
- package/src/shared/utf8-text.ts +21 -0
- package/src/tenancy/__tests__/static-tenant-source.test.ts +1 -0
- package/src/tenancy/static-tenant-source.ts +10 -2
- package/starter-packs/README.md +103 -0
- package/starter-packs/engineering/KnowledgeBase/About us.md +9 -0
- package/starter-packs/engineering/KnowledgeBase/Architecture.md +9 -0
- package/starter-packs/engineering/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/engineering/KnowledgeBase/How we ship.md +9 -0
- package/starter-packs/engineering/Plugins/engineering-starter/LICENSE +21 -0
- package/starter-packs/engineering/Plugins/engineering-starter/SOURCE.md +20 -0
- package/starter-packs/engineering/Plugins/engineering-starter/access.md +19 -0
- package/starter-packs/engineering/Plugins/engineering-starter/plugin.json +10 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/SKILL.md +171 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2a-startup-diagnostic.md +157 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2b-builder-brainstorm.md +39 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-ceo-review/SKILL.md +144 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-eng-review/SKILL.md +144 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/review/SKILL.md +80 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/review/checklist.md +184 -0
- package/starter-packs/engineering/pack.yaml +11 -0
- package/starter-packs/general/KnowledgeBase/About us.md +9 -0
- package/starter-packs/general/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/general/KnowledgeBase/How we work.md +9 -0
- package/starter-packs/general/Plugins/general-starter/LICENSE +202 -0
- package/starter-packs/general/Plugins/general-starter/SOURCE.md +10 -0
- package/starter-packs/general/Plugins/general-starter/access.md +19 -0
- package/starter-packs/general/Plugins/general-starter/plugin.json +10 -0
- package/starter-packs/general/Plugins/general-starter/skills/memory-management/SKILL.md +332 -0
- package/starter-packs/general/Plugins/general-starter/skills/productivity-start/SKILL.md +163 -0
- package/starter-packs/general/Plugins/general-starter/skills/productivity-update/SKILL.md +167 -0
- package/starter-packs/general/Plugins/general-starter/skills/task-management/SKILL.md +83 -0
- package/starter-packs/general/pack.yaml +10 -0
- package/starter-packs/operations/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/operations/KnowledgeBase/How we work.md +9 -0
- package/starter-packs/operations/KnowledgeBase/Processes.md +9 -0
- package/starter-packs/operations/KnowledgeBase/Tools we use.md +9 -0
- package/starter-packs/operations/KnowledgeBase/Vendors.md +9 -0
- package/starter-packs/operations/Plugins/operations-starter/LICENSE +202 -0
- package/starter-packs/operations/Plugins/operations-starter/SOURCE.md +10 -0
- package/starter-packs/operations/Plugins/operations-starter/access.md +19 -0
- package/starter-packs/operations/Plugins/operations-starter/plugin.json +10 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/capacity-plan/SKILL.md +111 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/change-request/SKILL.md +119 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/compliance-tracking/SKILL.md +44 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/process-doc/SKILL.md +80 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/process-optimization/SKILL.md +39 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/risk-assessment/SKILL.md +40 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/runbook/SKILL.md +85 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/status-report/SKILL.md +75 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/vendor-review/SKILL.md +102 -0
- package/starter-packs/operations/pack.yaml +12 -0
- package/starter-packs/sales/KnowledgeBase/About us.md +9 -0
- package/starter-packs/sales/KnowledgeBase/Customers.md +9 -0
- package/starter-packs/sales/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/sales/KnowledgeBase/How we sell.md +9 -0
- package/starter-packs/sales/KnowledgeBase/Objections.md +9 -0
- package/starter-packs/sales/KnowledgeBase/Pricing.md +9 -0
- package/starter-packs/sales/Plugins/sales-starter/LICENSE +202 -0
- package/starter-packs/sales/Plugins/sales-starter/SOURCE.md +10 -0
- package/starter-packs/sales/Plugins/sales-starter/access.md +19 -0
- package/starter-packs/sales/Plugins/sales-starter/plugin.json +10 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/account-context/SKILL.md +94 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/account-plan/SKILL.md +95 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/account-research/SKILL.md +99 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/account-tiering/SKILL.md +101 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/call-prep/SKILL.md +111 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/call-summary/SKILL.md +111 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/close-plan/SKILL.md +106 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/competitive-intelligence/SKILL.md +102 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/create-an-asset/SKILL.md +105 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/crm-hygiene-check/SKILL.md +103 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/customer-health/SKILL.md +108 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/customer-voice/SKILL.md +94 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/daily-briefing/SKILL.md +61 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/deal-advance-gap/SKILL.md +102 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/deal-review/SKILL.md +105 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/deal-signals/SKILL.md +96 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/deal-slip-scenario/SKILL.md +104 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/draft-outreach/SKILL.md +116 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/end-of-day/SKILL.md +114 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/expansion-whitespace/SKILL.md +97 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/forecast/SKILL.md +100 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/handle-objection/SKILL.md +93 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/inbox-sweep/SKILL.md +114 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/lead-triage/SKILL.md +106 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/log-activity/SKILL.md +111 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/pipeline-review/SKILL.md +97 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/renewal-radar/SKILL.md +95 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/rep-context/SKILL.md +90 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/route-lead/SKILL.md +111 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/sales-setup/SKILL.md +200 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/schedule-meeting/SKILL.md +99 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/stakeholder-map/SKILL.md +107 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/team-pipeline/SKILL.md +105 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/update-opportunity/SKILL.md +112 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/weekly-wrap/SKILL.md +90 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/win-loss-review/SKILL.md +88 -0
- package/starter-packs/sales/pack.yaml +13 -0
- package/starter-packs/support/KnowledgeBase/Escalations.md +9 -0
- package/starter-packs/support/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/support/KnowledgeBase/Known issues.md +9 -0
- package/starter-packs/support/KnowledgeBase/Products.md +9 -0
- package/starter-packs/support/KnowledgeBase/Tone of voice.md +9 -0
- package/starter-packs/support/Plugins/support-starter/LICENSE +202 -0
- package/starter-packs/support/Plugins/support-starter/SOURCE.md +10 -0
- package/starter-packs/support/Plugins/support-starter/access.md +19 -0
- package/starter-packs/support/Plugins/support-starter/plugin.json +10 -0
- package/starter-packs/support/Plugins/support-starter/skills/customer-escalation/SKILL.md +246 -0
- package/starter-packs/support/Plugins/support-starter/skills/customer-research/SKILL.md +250 -0
- package/starter-packs/support/Plugins/support-starter/skills/draft-response/SKILL.md +416 -0
- package/starter-packs/support/Plugins/support-starter/skills/kb-article/SKILL.md +350 -0
- package/starter-packs/support/Plugins/support-starter/skills/ticket-triage/SKILL.md +273 -0
- package/starter-packs/support/pack.yaml +12 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type { Server } from 'node:http';
|
|
2
|
+
import type { AddressInfo } from 'node:net';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import express from 'express';
|
|
7
|
+
import { describe, it, expect, afterEach, vi } from 'vitest';
|
|
8
|
+
|
|
9
|
+
import { AccessControlService } from '../access-control.service.js';
|
|
10
|
+
import { NodeFs } from '../../kb-fs/node-fs.js';
|
|
11
|
+
import type { WorkspaceService } from '../../workspace/workspace.service.js';
|
|
12
|
+
import type { AuthService } from '../../auth/auth.service.js';
|
|
13
|
+
import type { WorkflowService } from '../../workflow/workflow.service.js';
|
|
14
|
+
import type { WorkflowEventBus } from '../../workflow/event-bus.js';
|
|
15
|
+
import { createAccessRoutes } from '../access.routes.js';
|
|
16
|
+
import { usersDbDouble } from './users-db-double.js';
|
|
17
|
+
import { testKbContext } from '../../../__tests__/kb-context.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* `GET /api/access/admins`: who the admins are, for anyone signed in. It
|
|
21
|
+
* answers the Admin role's members — through a group too — and the deployment
|
|
22
|
+
* admins, each once, sorted by name, with names from the accounts table; and
|
|
23
|
+
* nothing else about roles. The roster stays admin-only.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const KB = 'knowledge-base';
|
|
27
|
+
const MEMBER = { id: 'u-lena', email: 'lena@acme.com', name: 'Lena Park' };
|
|
28
|
+
|
|
29
|
+
const ROLES = `roles:
|
|
30
|
+
Admin:
|
|
31
|
+
- sam.ortiz@acme.com
|
|
32
|
+
- group:Ops
|
|
33
|
+
Sales:
|
|
34
|
+
- felix@acme.com
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
const GROUPS = `groups:
|
|
38
|
+
Ops:
|
|
39
|
+
- dana@acme.com
|
|
40
|
+
- sam.ortiz@acme.com
|
|
41
|
+
Product:
|
|
42
|
+
- priya@acme.com
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
const tmpDirs: string[] = [];
|
|
46
|
+
|
|
47
|
+
async function makeHarness(
|
|
48
|
+
opts: {
|
|
49
|
+
roles?: string;
|
|
50
|
+
groups?: string;
|
|
51
|
+
signedIn?: boolean;
|
|
52
|
+
deploymentAdmins?: string[];
|
|
53
|
+
accounts?: { email: string; name?: string; deactivatedAt?: Date | null }[];
|
|
54
|
+
} = {},
|
|
55
|
+
): Promise<{ server: Server; baseUrl: string }> {
|
|
56
|
+
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-admins-route-'));
|
|
57
|
+
tmpDirs.push(workspaceDir);
|
|
58
|
+
const repoDir = path.join(workspaceDir, KB);
|
|
59
|
+
await fs.mkdir(repoDir, { recursive: true });
|
|
60
|
+
await fs.writeFile(path.join(repoDir, 'roles.yaml'), opts.roles ?? ROLES, 'utf-8');
|
|
61
|
+
await fs.writeFile(path.join(repoDir, 'groups.yaml'), opts.groups ?? GROUPS, 'utf-8');
|
|
62
|
+
|
|
63
|
+
const workspaceService = {
|
|
64
|
+
getOrCreateForBranch: vi.fn(async () => ({})),
|
|
65
|
+
getWorkspacePath: vi.fn(async () => workspaceDir),
|
|
66
|
+
readFile: vi.fn(async (_id: string, wsRel: string) => fs.readFile(path.join(workspaceDir, wsRel), 'utf-8')),
|
|
67
|
+
} as unknown as WorkspaceService;
|
|
68
|
+
// The real resolver: the admins read is its own membership answer. Nobody
|
|
69
|
+
// signed in here is an Admin, so nobody may read the roster either.
|
|
70
|
+
const deploymentAdmins = opts.deploymentAdmins ?? ['Owner@Acme.com'];
|
|
71
|
+
const accessControl = new AccessControlService(workspaceService, KB, new NodeFs(), deploymentAdmins);
|
|
72
|
+
const authService = { getUserById: vi.fn(async () => MEMBER) } as unknown as AuthService;
|
|
73
|
+
const db = usersDbDouble(
|
|
74
|
+
opts.accounts ?? [
|
|
75
|
+
{ email: 'dana@acme.com', name: 'Dana Admin' },
|
|
76
|
+
{ email: 'sam.ortiz@acme.com', name: 'Sam Ortiz' },
|
|
77
|
+
{ email: 'felix@acme.com', name: 'Felix Sales' },
|
|
78
|
+
{ email: 'priya@acme.com', name: 'Priya Product' },
|
|
79
|
+
MEMBER,
|
|
80
|
+
],
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const app = express();
|
|
84
|
+
app.use(express.json());
|
|
85
|
+
app.use('/api', (req, _res, next) => {
|
|
86
|
+
if (opts.signedIn !== false) (req as unknown as { userId: string }).userId = MEMBER.id;
|
|
87
|
+
next();
|
|
88
|
+
});
|
|
89
|
+
app.use(
|
|
90
|
+
'/api',
|
|
91
|
+
createAccessRoutes(
|
|
92
|
+
accessControl,
|
|
93
|
+
workspaceService,
|
|
94
|
+
authService,
|
|
95
|
+
authService,
|
|
96
|
+
{} as unknown as WorkflowService,
|
|
97
|
+
{ emit: vi.fn() } as unknown as WorkflowEventBus,
|
|
98
|
+
db,
|
|
99
|
+
testKbContext({ kbDirName: KB }),
|
|
100
|
+
deploymentAdmins,
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
const server = await new Promise<Server>((resolve) => {
|
|
104
|
+
const s = app.listen(0, () => resolve(s));
|
|
105
|
+
});
|
|
106
|
+
return { server, baseUrl: `http://127.0.0.1:${(server.address() as AddressInfo).port}` };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function close(s: Server): Promise<void> {
|
|
110
|
+
return new Promise((resolve, reject) => s.close((e) => (e ? reject(e) : resolve())));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
describe('GET /api/access/admins', () => {
|
|
114
|
+
let server: Server | null = null;
|
|
115
|
+
afterEach(async () => {
|
|
116
|
+
if (server) await close(server);
|
|
117
|
+
server = null;
|
|
118
|
+
await Promise.all(tmpDirs.splice(0).map((d) => fs.rm(d, { recursive: true, force: true })));
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('tells a Member every admin — direct, through a group, and the deployment admin — once each, sorted by name', async () => {
|
|
122
|
+
const h = await makeHarness();
|
|
123
|
+
server = h.server;
|
|
124
|
+
const res = await fetch(`${h.baseUrl}/api/access/admins`);
|
|
125
|
+
expect(res.status).toBe(200);
|
|
126
|
+
expect(await res.json()).toEqual({
|
|
127
|
+
admins: [
|
|
128
|
+
{ name: 'Dana Admin', email: 'dana@acme.com' },
|
|
129
|
+
// No account yet: named by address.
|
|
130
|
+
{ name: 'owner@acme.com', email: 'owner@acme.com' },
|
|
131
|
+
{ name: 'Sam Ortiz', email: 'sam.ortiz@acme.com' },
|
|
132
|
+
],
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('reveals nothing else: no other role, group or member, and no other field', async () => {
|
|
137
|
+
const h = await makeHarness();
|
|
138
|
+
server = h.server;
|
|
139
|
+
const text = await (await fetch(`${h.baseUrl}/api/access/admins`)).text();
|
|
140
|
+
for (const leak of ['felix', 'priya', 'Sales', 'Product', 'Ops', 'group:', 'lena']) {
|
|
141
|
+
expect(text).not.toContain(leak);
|
|
142
|
+
}
|
|
143
|
+
const body = JSON.parse(text) as { admins: Record<string, unknown>[] };
|
|
144
|
+
expect(Object.keys(body)).toEqual(['admins']);
|
|
145
|
+
for (const admin of body.admins) expect(Object.keys(admin).sort()).toEqual(['email', 'name']);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('answers only the deployment admins when roles.yaml does not parse, as the resolver does', async () => {
|
|
149
|
+
const h = await makeHarness({ roles: 'roles:\n Admin: not-a-list\n' });
|
|
150
|
+
server = h.server;
|
|
151
|
+
const body = (await (await fetch(`${h.baseUrl}/api/access/admins`)).json()) as { admins: unknown[] };
|
|
152
|
+
expect(body.admins).toEqual([{ name: 'owner@acme.com', email: 'owner@acme.com' }]);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('answers only the deployment admins when the resolver rejects roles.yaml the editing model would read', async () => {
|
|
156
|
+
// A role named with the reserved plugin prefix: the resolver refuses the
|
|
157
|
+
// whole file, so nobody in it is an Admin — and nobody is listed as one.
|
|
158
|
+
const h = await makeHarness({
|
|
159
|
+
roles: 'roles:\n Admin:\n - sam.ortiz@acme.com\n - group:Ops\n plugin/x:\n - felix@acme.com\n',
|
|
160
|
+
});
|
|
161
|
+
server = h.server;
|
|
162
|
+
const body = (await (await fetch(`${h.baseUrl}/api/access/admins`)).json()) as { admins: unknown[] };
|
|
163
|
+
expect(body.admins).toEqual([{ name: 'owner@acme.com', email: 'owner@acme.com' }]);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('does not count the members of a GROUP named Admin, as the resolver does not', async () => {
|
|
167
|
+
const h = await makeHarness({ groups: 'groups:\n Admin:\n - priya@acme.com\n' });
|
|
168
|
+
server = h.server;
|
|
169
|
+
const text = await (await fetch(`${h.baseUrl}/api/access/admins`)).text();
|
|
170
|
+
expect(text).not.toContain('priya');
|
|
171
|
+
expect(text).toContain('sam.ortiz@acme.com');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('leaves out a role member whose account is switched off, but not a switched-off deployment admin', async () => {
|
|
175
|
+
const off = new Date('2026-01-01T00:00:00Z');
|
|
176
|
+
const h = await makeHarness({
|
|
177
|
+
accounts: [
|
|
178
|
+
{ email: 'dana@acme.com', name: 'Dana Admin', deactivatedAt: off },
|
|
179
|
+
{ email: 'sam.ortiz@acme.com', name: 'Sam Ortiz' },
|
|
180
|
+
{ email: 'owner@acme.com', name: 'Olive Owner', deactivatedAt: off },
|
|
181
|
+
MEMBER,
|
|
182
|
+
],
|
|
183
|
+
});
|
|
184
|
+
server = h.server;
|
|
185
|
+
const body = (await (await fetch(`${h.baseUrl}/api/access/admins`)).json()) as { admins: unknown[] };
|
|
186
|
+
expect(body.admins).toEqual([
|
|
187
|
+
{ name: 'Olive Owner', email: 'owner@acme.com' },
|
|
188
|
+
{ name: 'Sam Ortiz', email: 'sam.ortiz@acme.com' },
|
|
189
|
+
]);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('refuses a caller who is not signed in', async () => {
|
|
193
|
+
const h = await makeHarness({ signedIn: false });
|
|
194
|
+
server = h.server;
|
|
195
|
+
expect((await fetch(`${h.baseUrl}/api/access/admins`)).status).toBe(401);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('leaves the roles roster admin-only', async () => {
|
|
199
|
+
const h = await makeHarness();
|
|
200
|
+
server = h.server;
|
|
201
|
+
expect((await fetch(`${h.baseUrl}/api/access/roles`)).status).toBe(403);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
@@ -28,11 +28,14 @@ interface Harness {
|
|
|
28
28
|
baseUrl: string;
|
|
29
29
|
canWriteBatch: ReturnType<typeof vi.fn>;
|
|
30
30
|
canOwnerBatch: ReturnType<typeof vi.fn>;
|
|
31
|
+
canReadBatch: ReturnType<typeof vi.fn>;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
// Alice WRITES both skills but is named in the `owner:` grant of one
|
|
34
|
+
// Alice WRITES both skills but is named in the `owner:` grant of one, and
|
|
35
|
+
// READS a folder she may not write.
|
|
34
36
|
const WRITABLE = new Set(['Skills/a/SKILL.md', 'Skills/b/SKILL.md']);
|
|
35
37
|
const OWNED = new Set(['Skills/a/SKILL.md']);
|
|
38
|
+
const READABLE = new Set(['Skills/a/SKILL.md', 'Skills/b/SKILL.md', 'KnowledgeBase/Sales']);
|
|
36
39
|
|
|
37
40
|
const verdicts = (set: Set<string>) =>
|
|
38
41
|
vi.fn(async (_w: string, _e: string, paths: string[]) => new Map(paths.map((p) => [p, set.has(p)])));
|
|
@@ -40,7 +43,8 @@ const verdicts = (set: Set<string>) =>
|
|
|
40
43
|
async function makeHarness(): Promise<Harness> {
|
|
41
44
|
const canWriteBatch = verdicts(WRITABLE);
|
|
42
45
|
const canOwnerBatch = verdicts(OWNED);
|
|
43
|
-
const
|
|
46
|
+
const canReadBatch = verdicts(READABLE);
|
|
47
|
+
const accessControl = { canWriteBatch, canOwnerBatch, canReadBatch } as unknown as IAccessControl;
|
|
44
48
|
|
|
45
49
|
const workspaceService = {} as unknown as WorkspaceService;
|
|
46
50
|
const authService = { getUserById: vi.fn(async () => USER) } as unknown as AuthService;
|
|
@@ -57,6 +61,7 @@ async function makeHarness(): Promise<Harness> {
|
|
|
57
61
|
accessControl,
|
|
58
62
|
workspaceService,
|
|
59
63
|
authService,
|
|
64
|
+
authService,
|
|
60
65
|
{} as unknown as WorkflowService,
|
|
61
66
|
{ emit: vi.fn() } as unknown as WorkflowEventBus,
|
|
62
67
|
usersDbDouble(),
|
|
@@ -68,7 +73,7 @@ async function makeHarness(): Promise<Harness> {
|
|
|
68
73
|
const s = app.listen(0, () => resolve(s));
|
|
69
74
|
});
|
|
70
75
|
const addr = server.address() as AddressInfo;
|
|
71
|
-
return { server, baseUrl: `http://127.0.0.1:${addr.port}`, canWriteBatch, canOwnerBatch };
|
|
76
|
+
return { server, baseUrl: `http://127.0.0.1:${addr.port}`, canWriteBatch, canOwnerBatch, canReadBatch };
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
function close(s: Server): Promise<void> {
|
|
@@ -119,16 +124,31 @@ describe('POST /access/batch', () => {
|
|
|
119
124
|
expect(h.canWriteBatch).not.toHaveBeenCalled();
|
|
120
125
|
});
|
|
121
126
|
|
|
127
|
+
it('answers READ for `verb: read` — a folder the caller may read but not write gets true', async () => {
|
|
128
|
+
h = await makeHarness();
|
|
129
|
+
|
|
130
|
+
const paths = [...PATHS, 'KnowledgeBase/Sales'];
|
|
131
|
+
const res = await post({ paths, verb: 'read' });
|
|
132
|
+
expect(res.status).toBe(200);
|
|
133
|
+
expect(await res.json()).toEqual({
|
|
134
|
+
results: { 'Skills/a/SKILL.md': true, 'Skills/b/SKILL.md': true, 'KnowledgeBase/Sales': true },
|
|
135
|
+
});
|
|
136
|
+
expect(h.canReadBatch).toHaveBeenCalledWith(WS, USER.email, paths);
|
|
137
|
+
expect(h.canWriteBatch).not.toHaveBeenCalled();
|
|
138
|
+
expect(h.canOwnerBatch).not.toHaveBeenCalled();
|
|
139
|
+
});
|
|
140
|
+
|
|
122
141
|
it('400s a verb it does not dispatch, resolving nothing', async () => {
|
|
123
142
|
h = await makeHarness();
|
|
124
143
|
|
|
125
|
-
for (const verb of ['
|
|
144
|
+
for (const verb of ['download', 'admin', 42, null]) {
|
|
126
145
|
const res = await post({ paths: PATHS, verb });
|
|
127
146
|
expect(res.status).toBe(400);
|
|
128
|
-
expect(await res.json()).toEqual({ error: 'verb must be "write" or "
|
|
147
|
+
expect(await res.json()).toEqual({ error: 'verb must be "write", "owner" or "read"' });
|
|
129
148
|
}
|
|
130
149
|
expect(h.canWriteBatch).not.toHaveBeenCalled();
|
|
131
150
|
expect(h.canOwnerBatch).not.toHaveBeenCalled();
|
|
151
|
+
expect(h.canReadBatch).not.toHaveBeenCalled();
|
|
132
152
|
});
|
|
133
153
|
|
|
134
154
|
it('keeps the paths validation ahead of the verb', async () => {
|
|
@@ -156,6 +156,7 @@ describe('access mutations on a file that cannot carry frontmatter', () => {
|
|
|
156
156
|
accessControl,
|
|
157
157
|
workspaceService,
|
|
158
158
|
{ getUserById: vi.fn(async () => USER) } as unknown as AuthService,
|
|
159
|
+
{ whileSwitchedOn: async (_email: string, write: () => Promise<void>) => write() },
|
|
159
160
|
workflowService,
|
|
160
161
|
{ emit: vi.fn() } as unknown as WorkflowEventBus,
|
|
161
162
|
usersDbDouble(),
|
|
@@ -88,7 +88,7 @@ async function makeHarness(opts: {
|
|
|
88
88
|
(req as unknown as { userId: string }).userId = USER.id;
|
|
89
89
|
next();
|
|
90
90
|
});
|
|
91
|
-
app.use('/api', createAccessRoutes(accessControl, workspaceService, authService, workflowService, eventBus, db, testKbContext({ kbDirName: KB })));
|
|
91
|
+
app.use('/api', createAccessRoutes(accessControl, workspaceService, authService, authService, workflowService, eventBus, db, testKbContext({ kbDirName: KB })));
|
|
92
92
|
|
|
93
93
|
const server = await new Promise<Server>((resolve) => {
|
|
94
94
|
const s = app.listen(0, () => resolve(s));
|
|
@@ -98,7 +98,7 @@ async function makeHarness(opts: {
|
|
|
98
98
|
});
|
|
99
99
|
app.use(
|
|
100
100
|
'/api',
|
|
101
|
-
createAccessRoutes(accessControl, workspaceService, authService, workflowService, eventBus, db, testKbContext({ kbDirName: KB })),
|
|
101
|
+
createAccessRoutes(accessControl, workspaceService, authService, authService, workflowService, eventBus, db, testKbContext({ kbDirName: KB })),
|
|
102
102
|
);
|
|
103
103
|
|
|
104
104
|
const server = await new Promise<Server>((resolve) => {
|
|
@@ -64,6 +64,7 @@ async function makeHarness(opts: { canRead?: boolean } = {}): Promise<Harness> {
|
|
|
64
64
|
accessControl,
|
|
65
65
|
{} as unknown as WorkspaceService,
|
|
66
66
|
{ getUserById: vi.fn(async () => USER) } as unknown as AuthService,
|
|
67
|
+
{ whileSwitchedOn: async (_email: string, write: () => Promise<void>) => write() },
|
|
67
68
|
{} as unknown as WorkflowService,
|
|
68
69
|
{ emit: vi.fn() } as unknown as WorkflowEventBus,
|
|
69
70
|
{} as unknown as Database,
|
|
@@ -134,7 +134,7 @@ async function makeHarness(opts: {
|
|
|
134
134
|
(req as unknown as { userId: string }).userId = USER.id;
|
|
135
135
|
next();
|
|
136
136
|
});
|
|
137
|
-
app.use('/api', createAccessRoutes(accessControl, workspaceService, authService, workflowService, eventBus, db, testKbContext({ kbDirName: KB })));
|
|
137
|
+
app.use('/api', createAccessRoutes(accessControl, workspaceService, authService, authService, workflowService, eventBus, db, testKbContext({ kbDirName: KB })));
|
|
138
138
|
|
|
139
139
|
const server = await new Promise<Server>((resolve) => {
|
|
140
140
|
const s = app.listen(0, () => resolve(s));
|
|
@@ -146,7 +146,7 @@ describe('SECURITY: Admin-role removal takes effect on the next request', () =>
|
|
|
146
146
|
(req as unknown as { userEmail?: string }).userEmail = req.header('x-test-email') ?? undefined;
|
|
147
147
|
next();
|
|
148
148
|
});
|
|
149
|
-
app.use('/api', createAccountRoutes(authService, adminAccess, erasure));
|
|
149
|
+
app.use('/api', createAccountRoutes(authService, adminAccess, erasure, { runAccountInvited: async () => [] }));
|
|
150
150
|
server = await new Promise<Server>((resolve) => {
|
|
151
151
|
const s = app.listen(0, () => resolve(s));
|
|
152
152
|
});
|
|
@@ -637,6 +637,18 @@ describe('Admin fixed members — the deployment admin', () => {
|
|
|
637
637
|
expect(roster.find((r) => r.canonical === 'sales')!.fixedMembers).toEqual([]);
|
|
638
638
|
});
|
|
639
639
|
|
|
640
|
+
it('sends the note with the roster, and the refusal leads with that same note', async () => {
|
|
641
|
+
// The page prints `fixedMembersNote` as sent, so this is the one copy of
|
|
642
|
+
// the wording: the note and the API refusal cannot drift apart.
|
|
643
|
+
const svc = serviceWith([OWNER]);
|
|
644
|
+
const roster = await svc.getRoster();
|
|
645
|
+
const note = roster.find((r) => r.canonical === 'admin')!.fixedMembersNote;
|
|
646
|
+
expect(note).toBe('Owner; cannot be removed here');
|
|
647
|
+
expect(roster.find((r) => r.canonical === 'sales')!.fixedMembersNote).toBeNull();
|
|
648
|
+
const refusal = await svc.addMember(ADMIN, 'admin', OWNER).catch((e: Error) => e);
|
|
649
|
+
expect((refusal as Error).message.startsWith(`${note}. `)).toBe(true);
|
|
650
|
+
});
|
|
651
|
+
|
|
640
652
|
it('canonicalises the configured address and drops a blank one', async () => {
|
|
641
653
|
// The composition root passes `[config.adminEmail]` unconditionally, so an
|
|
642
654
|
// unset ADMIN_EMAIL arrives as [''] — that must not become a member "".
|
|
@@ -665,7 +677,7 @@ describe('Admin fixed members — the deployment admin', () => {
|
|
|
665
677
|
status: 422,
|
|
666
678
|
payload: { kind: 'fixed-deployment-admin' },
|
|
667
679
|
message: expect.stringContaining(
|
|
668
|
-
'
|
|
680
|
+
'Owner; cannot be removed here',
|
|
669
681
|
),
|
|
670
682
|
});
|
|
671
683
|
// Nothing written: the file is byte-identical.
|
|
@@ -685,7 +697,7 @@ describe('Admin fixed members — the deployment admin', () => {
|
|
|
685
697
|
status: 422,
|
|
686
698
|
payload: { kind: 'fixed-deployment-admin' },
|
|
687
699
|
message: expect.stringContaining(
|
|
688
|
-
'
|
|
700
|
+
'Owner; cannot be removed here',
|
|
689
701
|
),
|
|
690
702
|
});
|
|
691
703
|
expect(await fs.readFile(path.join(repo, 'roles.yaml'), 'utf-8')).toContain(OWNER);
|
|
@@ -700,6 +712,7 @@ describe('Admin fixed members — the deployment admin', () => {
|
|
|
700
712
|
it('with no ADMIN_EMAIL configured, no role has a fixed member', async () => {
|
|
701
713
|
const roster = await serviceWith([]).getRoster();
|
|
702
714
|
expect(roster.every((r) => r.fixedMembers.length === 0)).toBe(true);
|
|
715
|
+
expect(roster.every((r) => r.fixedMembersNote === null)).toBe(true);
|
|
703
716
|
// ...and ordinary Admin membership stays editable.
|
|
704
717
|
await expect(serviceWith([]).addMember(ADMIN, 'admin', OWNER)).resolves.toBeDefined();
|
|
705
718
|
});
|
|
@@ -4,12 +4,14 @@ import fs from 'node:fs/promises';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import os from 'node:os';
|
|
6
6
|
import express from 'express';
|
|
7
|
+
import { PgDialect } from 'drizzle-orm/pg-core';
|
|
8
|
+
import type { SQL } from 'drizzle-orm';
|
|
7
9
|
import { describe, it, expect, afterEach, vi } from 'vitest';
|
|
8
10
|
import { NodeFs } from '../../kb-fs/node-fs.js';
|
|
9
11
|
|
|
10
12
|
import type { IAccessControl } from '../access-control.interface.js';
|
|
11
13
|
import type { WorkspaceService } from '../../workspace/workspace.service.js';
|
|
12
|
-
import
|
|
14
|
+
import { AuthService } from '../../auth/auth.service.js';
|
|
13
15
|
import type { WorkflowService } from '../../workflow/workflow.service.js';
|
|
14
16
|
import type { WorkflowEventBus } from '../../workflow/event-bus.js';
|
|
15
17
|
import type { FileTreeEntry } from '@bevel-software/platform-shared';
|
|
@@ -40,7 +42,9 @@ const ROLES = `roles:
|
|
|
40
42
|
/** Temp workspace dirs to clean up after each test. */
|
|
41
43
|
const tmpDirs: string[] = [];
|
|
42
44
|
|
|
43
|
-
async function makeHarness(
|
|
45
|
+
async function makeHarness(
|
|
46
|
+
opts: { isAdmin?: boolean; switchOffOnRolesLock?: string } = {},
|
|
47
|
+
): Promise<{ server: Server; baseUrl: string; txLog: string[] }> {
|
|
44
48
|
const isAdmin = opts.isAdmin ?? true;
|
|
45
49
|
|
|
46
50
|
// Back the harness with a REAL temp workspace dir: the service's single-file
|
|
@@ -103,8 +107,6 @@ async function makeHarness(opts: { isAdmin?: boolean } = {}): Promise<{ server:
|
|
|
103
107
|
}),
|
|
104
108
|
} as unknown as WorkspaceService;
|
|
105
109
|
|
|
106
|
-
const authService = { getUserById: vi.fn(async () => ADMIN) } as unknown as AuthService;
|
|
107
|
-
|
|
108
110
|
// Lock holder tracked so the service's getLock pre-check passes; the single-
|
|
109
111
|
// file write itself lands on disk via LockingFilesystem → LocalFilesystem.
|
|
110
112
|
// Strict per-path locks — a live row is contended even for the same user,
|
|
@@ -119,6 +121,8 @@ async function makeHarness(opts: { isAdmin?: boolean } = {}): Promise<{ server:
|
|
|
119
121
|
acquireLock: vi.fn(async (_w: string, _b: string, p: string, user: { id: string; name: string }) => {
|
|
120
122
|
const h = locks.get(p);
|
|
121
123
|
if (h) return { acquired: false, lock: lockRow(h) };
|
|
124
|
+
// An account switched off while the promotion waited for the lock.
|
|
125
|
+
if (p.endsWith('roles.yaml')) rolesLocked = true;
|
|
122
126
|
locks.set(p, user);
|
|
123
127
|
return { acquired: true, lock: lockRow(user) };
|
|
124
128
|
}),
|
|
@@ -130,7 +134,47 @@ async function makeHarness(opts: { isAdmin?: boolean } = {}): Promise<{ server:
|
|
|
130
134
|
} as unknown as WorkflowService;
|
|
131
135
|
|
|
132
136
|
const eventBus = { emit: vi.fn() } as unknown as WorkflowEventBus;
|
|
133
|
-
const
|
|
137
|
+
const accounts = [
|
|
138
|
+
{ email: 'off@example.com', deactivatedAt: new Date('2026-09-01T00:00:00Z') },
|
|
139
|
+
'on@example.com',
|
|
140
|
+
];
|
|
141
|
+
const before = usersDbDouble(accounts);
|
|
142
|
+
const after = usersDbDouble(
|
|
143
|
+
accounts.map((a) =>
|
|
144
|
+
a === opts.switchOffOnRolesLock ? { email: a, deactivatedAt: new Date('2026-10-10T00:00:00Z') } : a,
|
|
145
|
+
),
|
|
146
|
+
);
|
|
147
|
+
let rolesLocked = false;
|
|
148
|
+
const current = () => (rolesLocked ? after : before);
|
|
149
|
+
// What happened inside each users transaction, in order: the lock taken
|
|
150
|
+
// with `execute` (rendered), and what roles.yaml held when it ended.
|
|
151
|
+
const txLog: string[] = [];
|
|
152
|
+
const rolesOnDisk = () => fs.readFile(path.join(repoDir, 'roles.yaml'), 'utf-8');
|
|
153
|
+
const db = {
|
|
154
|
+
select: () => current().select(),
|
|
155
|
+
transaction: async <T>(cb: (tx: unknown) => Promise<T>): Promise<T> => {
|
|
156
|
+
txLog.push('begin');
|
|
157
|
+
const tx = {
|
|
158
|
+
execute: async (query: SQL) => {
|
|
159
|
+
const { sql, params } = new PgDialect().sqlToQuery(query);
|
|
160
|
+
txLog.push(`${sql} ${JSON.stringify(params)}`);
|
|
161
|
+
return [];
|
|
162
|
+
},
|
|
163
|
+
select: () => current().select(),
|
|
164
|
+
};
|
|
165
|
+
try {
|
|
166
|
+
return await cb(tx);
|
|
167
|
+
} finally {
|
|
168
|
+
const onDisk = await rolesOnDisk();
|
|
169
|
+
txLog.push(`end: ${['on@example.com', 'new@example.com'].filter((e) => onDisk.includes(e)).join(', ') || 'none'}`);
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
} as unknown as typeof before;
|
|
173
|
+
|
|
174
|
+
// The real auth service over the same double, so the promotion's switch
|
|
175
|
+
// lock and read run as they do in production (`whileSwitchedOn`).
|
|
176
|
+
const authService = new AuthService(db, { jwtSecret: 'test', adminEmail: '', adminPassword: '', allowedEmailDomains: [] });
|
|
177
|
+
vi.spyOn(authService, 'getUserById').mockImplementation(async () => ADMIN);
|
|
134
178
|
|
|
135
179
|
const app = express();
|
|
136
180
|
app.use(express.json());
|
|
@@ -138,13 +182,13 @@ async function makeHarness(opts: { isAdmin?: boolean } = {}): Promise<{ server:
|
|
|
138
182
|
(req as unknown as { userId: string }).userId = ADMIN.id;
|
|
139
183
|
next();
|
|
140
184
|
});
|
|
141
|
-
app.use('/api', createAccessRoutes(accessControl, workspaceService, authService, workflowService, eventBus, db, testKbContext({ kbDirName: KB })));
|
|
185
|
+
app.use('/api', createAccessRoutes(accessControl, workspaceService, authService, authService, workflowService, eventBus, db, testKbContext({ kbDirName: KB })));
|
|
142
186
|
|
|
143
187
|
const server = await new Promise<Server>((resolve) => {
|
|
144
188
|
const s = app.listen(0, () => resolve(s));
|
|
145
189
|
});
|
|
146
190
|
const addr = server.address() as AddressInfo;
|
|
147
|
-
return { server, baseUrl: `http://127.0.0.1:${addr.port}
|
|
191
|
+
return { server, baseUrl: `http://127.0.0.1:${addr.port}`, txLog };
|
|
148
192
|
}
|
|
149
193
|
|
|
150
194
|
function close(s: Server): Promise<void> {
|
|
@@ -258,4 +302,85 @@ describe('/api/access/roles routes', () => {
|
|
|
258
302
|
const body = (await add.json()) as { roles: { canonical: string; members: string[] }[] };
|
|
259
303
|
expect(body.roles.find((r) => r.canonical === 'sales')!.members).toContain('mkt@example.com');
|
|
260
304
|
});
|
|
305
|
+
|
|
306
|
+
// The invite's promotion (`ifActive`) reads whether the account is switched
|
|
307
|
+
// off inside the roles lock, not from the list the dialog read before the
|
|
308
|
+
// send nor before the lock was waited for.
|
|
309
|
+
async function addAdmin(baseUrl: string, body: unknown) {
|
|
310
|
+
return fetch(`${baseUrl}/api/access/roles/admin/members`, {
|
|
311
|
+
method: 'POST',
|
|
312
|
+
headers: { 'Content-Type': 'application/json' },
|
|
313
|
+
body: JSON.stringify(body),
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
it('ifActive refuses a switched-off account (409 deactivated) and writes nothing', async () => {
|
|
318
|
+
const h = await makeHarness();
|
|
319
|
+
server = h.server;
|
|
320
|
+
const res = await addAdmin(h.baseUrl, { email: 'Off@Example.com', ifActive: true });
|
|
321
|
+
expect(res.status).toBe(409);
|
|
322
|
+
expect(await res.json()).toEqual({ error: 'This account is switched off', kind: 'deactivated' });
|
|
323
|
+
const roster = (await (await fetch(`${h.baseUrl}/api/access/roles`)).json()) as {
|
|
324
|
+
roles: { canonical: string; members: string[] }[];
|
|
325
|
+
};
|
|
326
|
+
expect(roster.roles.find((r) => r.canonical === 'admin')!.members).not.toContain('off@example.com');
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('ifActive promotes a switched-on account, and one with no account yet', async () => {
|
|
330
|
+
const h = await makeHarness();
|
|
331
|
+
server = h.server;
|
|
332
|
+
expect((await addAdmin(h.baseUrl, { email: 'on@example.com', ifActive: true })).status).toBe(200);
|
|
333
|
+
const res = await addAdmin(h.baseUrl, { email: 'new@example.com', ifActive: true });
|
|
334
|
+
expect(res.status).toBe(200);
|
|
335
|
+
const body = (await res.json()) as { roles: { canonical: string; members: string[] }[] };
|
|
336
|
+
expect(body.roles.find((r) => r.canonical === 'admin')!.members).toEqual(
|
|
337
|
+
expect.arrayContaining(['on@example.com', 'new@example.com']),
|
|
338
|
+
);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('ifActive reads the account once the roles lock is held: one switched off while it waited is refused', async () => {
|
|
342
|
+
const h = await makeHarness({ switchOffOnRolesLock: 'on@example.com' });
|
|
343
|
+
server = h.server;
|
|
344
|
+
const res = await addAdmin(h.baseUrl, { email: 'on@example.com', ifActive: true });
|
|
345
|
+
expect(res.status).toBe(409);
|
|
346
|
+
expect(await res.json()).toEqual({ error: 'This account is switched off', kind: 'deactivated' });
|
|
347
|
+
const roster = (await (await fetch(`${h.baseUrl}/api/access/roles`)).json()) as {
|
|
348
|
+
roles: { canonical: string; members: string[] }[];
|
|
349
|
+
};
|
|
350
|
+
expect(roster.roles.find((r) => r.canonical === 'admin')!.members).not.toContain('on@example.com');
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// Every switch-off (and an account created switched off) takes the same
|
|
354
|
+
// lock first; see account-deactivation.test for that side.
|
|
355
|
+
it.each([
|
|
356
|
+
['an existing account', 'On@Example.com', 'on@example.com'],
|
|
357
|
+
['an address with no account yet', 'new@example.com', 'new@example.com'],
|
|
358
|
+
])(
|
|
359
|
+
'ifActive holds the address switch lock until roles.yaml is written, for %s',
|
|
360
|
+
async (_label, email, canonical) => {
|
|
361
|
+
const h = await makeHarness();
|
|
362
|
+
server = h.server;
|
|
363
|
+
expect((await addAdmin(h.baseUrl, { email, ifActive: true })).status).toBe(200);
|
|
364
|
+
expect(h.txLog).toEqual([
|
|
365
|
+
'begin',
|
|
366
|
+
`select set_config('lock_timeout', $1, true) ["15000"]`,
|
|
367
|
+
`select pg_advisory_xact_lock(hashtext($1)) ["account-switch:${canonical}"]`,
|
|
368
|
+
`end: ${canonical}`,
|
|
369
|
+
]);
|
|
370
|
+
},
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
it('without ifActive, adding a switched-off account works as before', async () => {
|
|
374
|
+
const h = await makeHarness();
|
|
375
|
+
server = h.server;
|
|
376
|
+
expect((await addAdmin(h.baseUrl, { email: 'off@example.com' })).status).toBe(200);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('refuses a non-boolean ifActive (400)', async () => {
|
|
380
|
+
const h = await makeHarness();
|
|
381
|
+
server = h.server;
|
|
382
|
+
for (const ifActive of ['true', 1, null]) {
|
|
383
|
+
expect((await addAdmin(h.baseUrl, { email: 'on@example.com', ifActive })).status).toBe(400);
|
|
384
|
+
}
|
|
385
|
+
});
|
|
261
386
|
});
|
|
@@ -329,11 +329,11 @@ describe('UserAccessRemovalService', () => {
|
|
|
329
329
|
}
|
|
330
330
|
});
|
|
331
331
|
|
|
332
|
-
it('refuses the
|
|
332
|
+
it('refuses the owner and the last Admin, writing nothing', async () => {
|
|
333
333
|
const { service, workflow } = build(stubWorkflow(), ['Lee@x.io']);
|
|
334
334
|
const report = await service.report(LEE);
|
|
335
335
|
expect(report.removable).toBe(false);
|
|
336
|
-
expect(report.blockedReason).
|
|
336
|
+
expect(report.blockedReason).toBe('The owner stays in roles and access rules.');
|
|
337
337
|
await expect(service.remove(ADMIN, LEE, 'deleted-1')).rejects.toMatchObject({ status: 409 });
|
|
338
338
|
|
|
339
339
|
const other = build(stubWorkflow(), []);
|
|
@@ -48,12 +48,13 @@ function boundValues(node: unknown, depth = 0): string[] {
|
|
|
48
48
|
const canonical = (email: string) => email.trim().toLowerCase();
|
|
49
49
|
|
|
50
50
|
export function usersDbDouble(
|
|
51
|
-
accounts: readonly (string | { email: string; name?: string })[] = [],
|
|
51
|
+
accounts: readonly (string | { email: string; name?: string; deactivatedAt?: Date | null })[] = [],
|
|
52
52
|
): Database {
|
|
53
53
|
const rows = accounts.map((a) => {
|
|
54
54
|
const email = typeof a === 'string' ? a : a.email;
|
|
55
55
|
const name = typeof a === 'string' ? undefined : a.name;
|
|
56
|
-
|
|
56
|
+
const deactivatedAt = typeof a === 'string' ? null : (a.deactivatedAt ?? null);
|
|
57
|
+
return { id: `u-${email}`, email, name: name ?? email.split('@')[0], deactivatedAt };
|
|
57
58
|
});
|
|
58
59
|
const from = () => {
|
|
59
60
|
// Thenable so a bare `await db.select().from(users)` resolves, with
|
|
@@ -68,5 +69,13 @@ export function usersDbDouble(
|
|
|
68
69
|
};
|
|
69
70
|
return query;
|
|
70
71
|
};
|
|
71
|
-
|
|
72
|
+
const db = {
|
|
73
|
+
select: () => ({ from }),
|
|
74
|
+
// One connection, no isolation to model: the callback runs on the same
|
|
75
|
+
// double.
|
|
76
|
+
transaction: async <T>(cb: (tx: unknown) => Promise<T>) => cb(db),
|
|
77
|
+
// Locks taken with `execute` (advisory locks) answer nothing.
|
|
78
|
+
execute: async () => [],
|
|
79
|
+
};
|
|
80
|
+
return db as unknown as Database;
|
|
72
81
|
}
|