@bevel-software/platform-core-backend 0.26.0 → 0.27.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 +54 -3
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +26 -0
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +100 -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 +8 -0
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +1 -0
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +11 -0
- package/dist/modules/access/access-control.service.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/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.routes.d.ts.map +1 -1
- package/dist/modules/auth/account.routes.js +10 -3
- 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/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/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 +805 -106
- 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/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 +67 -3
- package/src/core/create-core-services.ts +140 -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.service.test.ts +19 -0
- package/src/modules/access/access-control.interface.ts +13 -0
- package/src/modules/access/access-control.service.ts +16 -0
- package/src/modules/access/directory-sync-bot.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.routes.test.ts +5 -3
- package/src/modules/auth/account.routes.ts +10 -3
- package/src/modules/auth/auth.middleware.ts +1 -1
- 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/core-schema.ts +33 -3
- 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 +879 -112
- 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,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Starter packs: the pages and the plugin a new knowledge base can be filled
|
|
3
|
+
* with in one click, chosen by what the team does.
|
|
4
|
+
*
|
|
5
|
+
* A pack is a folder under the packs root (`starter-packs/` in this package,
|
|
6
|
+
* or a distribution's `STARTER_PACKS_DIR`):
|
|
7
|
+
*
|
|
8
|
+
* <id>/pack.yaml id, name, description, order, firstPagePrompt, suggestedPages
|
|
9
|
+
* <id>/KnowledgeBase/… pages, under the DEFAULT layout's folder names
|
|
10
|
+
* <id>/Plugins/<name>/… a team plugin in the normal plugin layout
|
|
11
|
+
* <id>/Skills/… shared skills, if a pack carries any
|
|
12
|
+
*
|
|
13
|
+
* The three folders are written under the deployment's OWN names for them
|
|
14
|
+
* (`KnowledgeBase/` lands in whatever `knowledgeBaseDir` is), so a pack is
|
|
15
|
+
* authored once and fits every layout. Anything else in a pack folder (a
|
|
16
|
+
* README, notes for whoever maintains it) is the pack's, not the knowledge
|
|
17
|
+
* base's, and is never copied.
|
|
18
|
+
*
|
|
19
|
+
* A pack that does not read as one — no `pack.yaml`, a field missing or of
|
|
20
|
+
* the wrong kind, an id that is not its folder's name — is passed over with
|
|
21
|
+
* a warning: one broken folder must not take the choice away from everyone.
|
|
22
|
+
*
|
|
23
|
+
* The disk is read through the one walk and the one probe
|
|
24
|
+
* (`shared/fs.contract.ts`), handed in by the caller; only a file's bytes are
|
|
25
|
+
* read here, which neither contract covers.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import fs from 'node:fs/promises';
|
|
29
|
+
import path from 'node:path';
|
|
30
|
+
import { parse as parseYaml } from 'yaml';
|
|
31
|
+
import { renderKbLayoutPlaceholders, type KbLayout } from '@bevel-software/platform-shared';
|
|
32
|
+
import type { IFsProbe, ITreeWalker, WalkedEntry } from '../../shared/fs.contract.js';
|
|
33
|
+
import { logger } from '../../shared/logging.js';
|
|
34
|
+
import { utf8Text } from '../../shared/utf8-text.js';
|
|
35
|
+
|
|
36
|
+
const log = logger('starter-packs');
|
|
37
|
+
|
|
38
|
+
/** The file that makes a folder a pack. */
|
|
39
|
+
export const PACK_MANIFEST = 'pack.yaml';
|
|
40
|
+
|
|
41
|
+
/** The answer that chooses no pack: "Skip, I'll start from scratch". Never a pack's id. */
|
|
42
|
+
export const NO_STARTER_PACK = 'none';
|
|
43
|
+
|
|
44
|
+
/** A pack id: the folder's name, kebab-case, so it is safe in a URL, a setting and a path. */
|
|
45
|
+
const PACK_ID_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
46
|
+
|
|
47
|
+
/** The pack's own folders, by their default-layout names, and the layout key each one is written under. */
|
|
48
|
+
export const PACK_ROOTS: ReadonlyArray<readonly [string, keyof Pick<KbLayout, 'knowledgeBaseDir' | 'pluginsDir' | 'skillsDir'>]> = [
|
|
49
|
+
['KnowledgeBase', 'knowledgeBaseDir'],
|
|
50
|
+
['Plugins', 'pluginsDir'],
|
|
51
|
+
['Skills', 'skillsDir'],
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
/** What a chip shows, and the order the chips come in. */
|
|
55
|
+
export interface StarterPackSummary {
|
|
56
|
+
id: string;
|
|
57
|
+
name: string;
|
|
58
|
+
description: string;
|
|
59
|
+
order: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface StarterPack extends StarterPackSummary {
|
|
63
|
+
/** Replaces the generic "write your first page" request for a team that chose this pack. */
|
|
64
|
+
firstPagePrompt: string;
|
|
65
|
+
/** The pages the agent's first-run note offers to draft. */
|
|
66
|
+
suggestedPages: string[];
|
|
67
|
+
/** The pack's folder, absolute. */
|
|
68
|
+
dir: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** One file a pack would add, at the path it would have in the knowledge base. */
|
|
72
|
+
export interface StarterPackFile {
|
|
73
|
+
/** Repository-relative, under the deployment's layout: `KnowledgeBase/About us.md`, `Plugins/x/skills/y/SKILL.md`. */
|
|
74
|
+
repoPath: string;
|
|
75
|
+
/** Which of the pack's folders it came from, by its default name. */
|
|
76
|
+
root: (typeof PACK_ROOTS)[number][0];
|
|
77
|
+
/** The bytes to write: text with the layout placeholders filled, or the file as it is. */
|
|
78
|
+
content: string | Buffer;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Every valid pack under `root`, chip order first (then name). A root that
|
|
83
|
+
* is not there offers no packs; it is not an error, since a distribution may
|
|
84
|
+
* ship none.
|
|
85
|
+
*/
|
|
86
|
+
export async function loadStarterPacks(disk: IFsProbe, root: string): Promise<StarterPack[]> {
|
|
87
|
+
let entries: WalkedEntry[] | null;
|
|
88
|
+
try {
|
|
89
|
+
entries = await disk.listDir(root);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
log.warn(`could not read the starter packs folder "${root}" — no packs offered:`, { err });
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
if (entries === null) return [];
|
|
95
|
+
const packs: StarterPack[] = [];
|
|
96
|
+
for (const entry of entries) {
|
|
97
|
+
if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
|
|
98
|
+
const pack = await readStarterPack(disk, path.join(root, entry.name));
|
|
99
|
+
if (pack) packs.push(pack);
|
|
100
|
+
}
|
|
101
|
+
return packs.sort((a, b) => a.order - b.order || a.name.localeCompare(b.name));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** The pack in `dir`, or null (with a warning saying why) when it does not read as one. */
|
|
105
|
+
export async function readStarterPack(disk: IFsProbe, dir: string): Promise<StarterPack | null> {
|
|
106
|
+
const folder = path.basename(dir);
|
|
107
|
+
let raw: string;
|
|
108
|
+
try {
|
|
109
|
+
raw = await disk.readTextFile(path.join(dir, PACK_MANIFEST));
|
|
110
|
+
} catch {
|
|
111
|
+
log.warn(`starter pack "${folder}" has no readable ${PACK_MANIFEST} — skipped.`);
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
let parsed: unknown;
|
|
115
|
+
try {
|
|
116
|
+
parsed = parseYaml(raw);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
log.warn(`starter pack "${folder}": ${PACK_MANIFEST} is not valid YAML — skipped.`, {
|
|
119
|
+
error: err instanceof Error ? err.message : String(err),
|
|
120
|
+
});
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const problem = packProblem(parsed, folder);
|
|
124
|
+
if (problem) {
|
|
125
|
+
log.warn(`starter pack "${folder}": ${problem} — skipped.`);
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
const m = parsed as Record<string, unknown>;
|
|
129
|
+
return {
|
|
130
|
+
id: m.id as string,
|
|
131
|
+
name: (m.name as string).trim(),
|
|
132
|
+
description: (m.description as string).trim(),
|
|
133
|
+
order: m.order as number,
|
|
134
|
+
firstPagePrompt: (m.firstPagePrompt as string).trim(),
|
|
135
|
+
suggestedPages: (m.suggestedPages as string[]).map((p) => p.trim()),
|
|
136
|
+
dir,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Why a parsed `pack.yaml` is not a pack, or null when it is one. */
|
|
141
|
+
export function packProblem(manifest: unknown, folder: string): string | null {
|
|
142
|
+
if (typeof manifest !== 'object' || manifest === null || Array.isArray(manifest)) {
|
|
143
|
+
return `${PACK_MANIFEST} is not a mapping`;
|
|
144
|
+
}
|
|
145
|
+
const m = manifest as Record<string, unknown>;
|
|
146
|
+
if (typeof m.id !== 'string' || !PACK_ID_RE.test(m.id)) return '`id` must be lowercase letters, digits and hyphens';
|
|
147
|
+
if (m.id !== folder) return `\`id\` ("${m.id}") must be the folder's name`;
|
|
148
|
+
if (m.id === NO_STARTER_PACK) return `"${NO_STARTER_PACK}" is reserved for skipping`;
|
|
149
|
+
for (const key of ['name', 'description', 'firstPagePrompt'] as const) {
|
|
150
|
+
if (typeof m[key] !== 'string' || !(m[key] as string).trim()) return `\`${key}\` must be a non-empty string`;
|
|
151
|
+
}
|
|
152
|
+
if (typeof m.order !== 'number' || !Number.isFinite(m.order)) return '`order` must be a number';
|
|
153
|
+
if (!Array.isArray(m.suggestedPages) || m.suggestedPages.some((p) => typeof p !== 'string' || !p.trim())) {
|
|
154
|
+
return '`suggestedPages` must be a list of page names';
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Every file the pack would add, at its path under `layout`, in walk order.
|
|
161
|
+
* The knowledge-base walk lists them: dot-entries are left behind (an
|
|
162
|
+
* editor's or an OS's droppings, never content), and so is anything that is
|
|
163
|
+
* not a regular file — a pack is files. A pack folder that is not there, or
|
|
164
|
+
* is a file where a folder was expected, holds nothing; a folder in one that
|
|
165
|
+
* cannot be listed is the error, since a pack with a hole is not the pack.
|
|
166
|
+
*/
|
|
167
|
+
export async function starterPackFiles(disk: ITreeWalker, pack: StarterPack, layout: Required<KbLayout>): Promise<StarterPackFile[]> {
|
|
168
|
+
const out: StarterPackFile[] = [];
|
|
169
|
+
for (const [root, layoutKey] of PACK_ROOTS) {
|
|
170
|
+
const base = path.join(pack.dir, root);
|
|
171
|
+
for (const rel of await disk.walkFiles(base, () => true, { strict: true })) {
|
|
172
|
+
const bytes = await fs.readFile(path.join(base, ...rel.split('/')));
|
|
173
|
+
const text = utf8Text(bytes);
|
|
174
|
+
out.push({
|
|
175
|
+
repoPath: `${layout[layoutKey]}/${rel}`,
|
|
176
|
+
root,
|
|
177
|
+
content: text === null ? bytes : renderKbLayoutPlaceholders(text, layout),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
@@ -152,11 +152,12 @@ describe('what my_plugin tells an agent about the personal plugin', () => {
|
|
|
152
152
|
const def = myPluginDef(DEFAULT_KB_LAYOUT);
|
|
153
153
|
expect((def.tool_call_template as { url: string; http_method: string }).url).toBe('${API_URL}/api/plugins/personal');
|
|
154
154
|
expect((def.tool_call_template as { http_method: string }).http_method).toBe('POST');
|
|
155
|
-
// The flat inputs are wrapped under `body` by `toolDef`; `my_plugin` takes
|
|
155
|
+
// The flat inputs are wrapped under `body` by `toolDef`; `my_plugin` takes
|
|
156
|
+
// none, so the envelope is optional too and `my_plugin({})` is a call
|
|
157
|
+
// that matches (see `bodyEnvelope`).
|
|
156
158
|
expect(def.inputs).toEqual({
|
|
157
159
|
type: 'object',
|
|
158
160
|
properties: { body: { type: 'object', properties: {}, additionalProperties: false } },
|
|
159
|
-
required: ['body'],
|
|
160
161
|
additionalProperties: false,
|
|
161
162
|
});
|
|
162
163
|
expect(Object.keys((def.outputs as { properties: Record<string, unknown> }).properties)).toEqual([
|
|
@@ -279,4 +280,26 @@ describe('the creation endpoints behind the key-or-session gate', () => {
|
|
|
279
280
|
expect(refused.status).toBe(404);
|
|
280
281
|
expect(await refused.json()).toEqual({ error: 'There is no folder "Nope" under Plugins/.' });
|
|
281
282
|
});
|
|
283
|
+
|
|
284
|
+
it('refuses an agent call whose arguments do not match the tool, before anything is provisioned', async () => {
|
|
285
|
+
// These two routes ARE the tools, so they check a call against what the
|
|
286
|
+
// tools declare, as every route-hosted tool does. `my_plugin` takes no
|
|
287
|
+
// arguments; `create_plugin` takes a non-empty `name` and an optional `parent`.
|
|
288
|
+
myPluginDef(DEFAULT_KB_LAYOUT);
|
|
289
|
+
const base = await start();
|
|
290
|
+
const stray = await post(base, '/api/plugins/personal', { unexpected: 1 }, 'bevel_alice');
|
|
291
|
+
expect(stray.status).toBe(400);
|
|
292
|
+
const strayBody = (await stray.json()) as { kind: string; error: string };
|
|
293
|
+
expect(strayBody.kind).toBe('arguments-do-not-match');
|
|
294
|
+
expect(strayBody.error).toContain('"unexpected" is not an argument of this tool.');
|
|
295
|
+
expect(strayBody.error.split('\n').pop()).toBe('Call: KNOWLEDGE_BASE.my_plugin({})');
|
|
296
|
+
expect(ensurePersonalPlugin).not.toHaveBeenCalled();
|
|
297
|
+
|
|
298
|
+
const empty = await post(base, '/api/plugins', { name: '' }, 'bevel_alice');
|
|
299
|
+
expect(empty.status).toBe(400);
|
|
300
|
+
expect(((await empty.json()) as { error: string }).error).toContain(
|
|
301
|
+
'"name" must be at least 1 character(s) long, but 0 was given.',
|
|
302
|
+
);
|
|
303
|
+
expect(createPlugin).not.toHaveBeenCalled();
|
|
304
|
+
});
|
|
282
305
|
});
|
|
@@ -151,6 +151,20 @@ export class PluginProvisionService {
|
|
|
151
151
|
*/
|
|
152
152
|
private readonly creations = new WorkspaceMutex();
|
|
153
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Run `fn` holding the creation lock of every plugin name in `names` — for
|
|
156
|
+
* a writer outside this service that publishes plugins of its own (a
|
|
157
|
+
* starter pack) and whose name check and commit must be one step against
|
|
158
|
+
* a creation or a deletion of the same name. The set is taken in one
|
|
159
|
+
* `runAll`; `fn` must not take this lock again (see `WorkspaceMutex`).
|
|
160
|
+
*/
|
|
161
|
+
withIdentities<T>(names: string[], fn: () => Promise<T>): Promise<T> {
|
|
162
|
+
return this.creations.runAll(
|
|
163
|
+
names.map((name) => `plugin:${pluginManifestName(name)}`),
|
|
164
|
+
fn,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
154
168
|
constructor(
|
|
155
169
|
private readonly workspaceService: WorkspaceService,
|
|
156
170
|
private readonly commits: ProvisionCommitDriver,
|
|
@@ -693,7 +707,14 @@ export function personalAccessMd(creator: { name: string; email: string }): stri
|
|
|
693
707
|
return spliceGrant(seeded, 'read', creatorPrincipal(creator), { allowScalar: false, target: 'node' }).text;
|
|
694
708
|
}
|
|
695
709
|
|
|
696
|
-
|
|
710
|
+
/**
|
|
711
|
+
* `base` — an access.md in the two-block shape — with `creator` named under
|
|
712
|
+
* `read`, `write` and `owner` of the folder: how a plugin made by someone is
|
|
713
|
+
* run by them. Exported for the one other door that brings a plugin into
|
|
714
|
+
* being, a starter pack (`modules/onboarding`), whose shipped rules it adds
|
|
715
|
+
* the admin who applied the pack to.
|
|
716
|
+
*/
|
|
717
|
+
export function withCreatorGrants(base: string, creator: { name: string; email: string }): string {
|
|
697
718
|
const principal = creatorPrincipal(creator);
|
|
698
719
|
let out = base;
|
|
699
720
|
for (const verb of ['read', 'write', 'owner'] as const) {
|
|
@@ -16,6 +16,7 @@ import { domainErrorBody } from '../../shared/http-errors.js';
|
|
|
16
16
|
import { pluginFolderBelowRoot } from './plugins.service.js';
|
|
17
17
|
import type { KbContext } from '../../shared/kb-context.js';
|
|
18
18
|
import { PluginProvisionError, type PluginProvisionService } from './plugin-provision.service.js';
|
|
19
|
+
import { argumentsRefusal } from '../tool-helpers/route-argument-check.js';
|
|
19
20
|
import { PluginLinkError, type PluginLinksService } from './plugin-links.service.js';
|
|
20
21
|
import { PluginRenameError, type PluginRenameService } from './plugin-rename.service.js';
|
|
21
22
|
import type { JoinRequestsService } from './join-requests.service.js';
|
|
@@ -64,6 +65,24 @@ import type {
|
|
|
64
65
|
* middleware change can never silently un-gate one. Nothing here is reachable
|
|
65
66
|
* with an agent connection key or a manual-auth bearer.
|
|
66
67
|
*/
|
|
68
|
+
/**
|
|
69
|
+
* Answer the arguments-do-not-match refusal when the body does not match the
|
|
70
|
+
* tool hosted at this route; `true` when it answered. A body that is not a
|
|
71
|
+
* JSON object is refused here too, with the sentence `toolHandler` uses, so a
|
|
72
|
+
* `null` or an array never reaches the handler as if it were `{}`.
|
|
73
|
+
*/
|
|
74
|
+
function refuseMismatchedArguments(req: express.Request, res: express.Response): boolean {
|
|
75
|
+
const body: unknown = req.body === undefined ? {} : req.body;
|
|
76
|
+
if (typeof body !== 'object' || body === null || Array.isArray(body)) {
|
|
77
|
+
res.status(400).json({ error: 'Request body must be a JSON object.' });
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
const mismatch = argumentsRefusal(req.originalUrl, body as Record<string, unknown>, req.query);
|
|
81
|
+
if (!mismatch) return false;
|
|
82
|
+
res.status(mismatch.status).json({ ...mismatch.details, error: mismatch.message });
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
67
86
|
/**
|
|
68
87
|
* The two doors through which plugin folders come to exist — ONE
|
|
69
88
|
* implementation each, for the app and for agents alike:
|
|
@@ -96,6 +115,11 @@ export function createPluginCreationRoutes(
|
|
|
96
115
|
res.status(401).json({ error: 'Unauthenticated' });
|
|
97
116
|
return;
|
|
98
117
|
}
|
|
118
|
+
// These two routes ARE the `create_plugin` and `my_plugin` tools, so an
|
|
119
|
+
// agent's call is checked against what those tools declare, exactly as a
|
|
120
|
+
// route-hosted tool's is in `toolHandler`: a call that does not match is
|
|
121
|
+
// refused before anything is provisioned.
|
|
122
|
+
if (refuseMismatchedArguments(req, res)) return;
|
|
99
123
|
// `req.body` is undefined when no JSON body was sent at all — that is a
|
|
100
124
|
// 400, not a destructuring crash.
|
|
101
125
|
const { name, parent } = (req.body ?? {}) as { name?: string; parent?: unknown };
|
|
@@ -129,6 +153,11 @@ export function createPluginCreationRoutes(
|
|
|
129
153
|
res.status(401).json({ error: 'Unauthenticated' });
|
|
130
154
|
return;
|
|
131
155
|
}
|
|
156
|
+
// These two routes ARE the `create_plugin` and `my_plugin` tools, so an
|
|
157
|
+
// agent's call is checked against what those tools declare, exactly as a
|
|
158
|
+
// route-hosted tool's is in `toolHandler`: a call that does not match is
|
|
159
|
+
// refused before anything is provisioned.
|
|
160
|
+
if (refuseMismatchedArguments(req, res)) return;
|
|
132
161
|
try {
|
|
133
162
|
res.json(await provision.ensurePersonalPlugin(user));
|
|
134
163
|
} catch (err) {
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import type { SQL } from 'drizzle-orm';
|
|
4
|
+
import { PgDialect } from 'drizzle-orm/pg-core';
|
|
3
5
|
import {
|
|
4
6
|
DeploymentSettingsService,
|
|
5
7
|
SettingsValidationError,
|
|
6
8
|
CORE_SETTINGS,
|
|
7
9
|
LEGACY_LAYOUT_ENV_VARS,
|
|
10
|
+
retireMergedBranchesOn,
|
|
8
11
|
} from '../deployment-settings.service.js';
|
|
9
12
|
import type { Database } from '../../database/connection.js';
|
|
10
13
|
|
|
@@ -28,13 +31,54 @@ function makeDb() {
|
|
|
28
31
|
else rows.push({ key: v.key, value: v.value, encrypted: v.encrypted });
|
|
29
32
|
return Promise.resolve();
|
|
30
33
|
},
|
|
34
|
+
onConflictDoNothing: () => ({
|
|
35
|
+
returning: () => {
|
|
36
|
+
if (rows.some((r) => r.key === v.key)) return Promise.resolve([]);
|
|
37
|
+
rows.push({ key: v.key, value: v.value, encrypted: v.encrypted });
|
|
38
|
+
return Promise.resolve([{ key: v.key }]);
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
}),
|
|
42
|
+
}),
|
|
43
|
+
// `delete … where … returning` / `update … set … where … returning`: the
|
|
44
|
+
// conditional swap. The fake applies the one predicate the service sends,
|
|
45
|
+
// key AND value, by reading the bound parameters off the drizzle `where`.
|
|
46
|
+
delete: () => ({
|
|
47
|
+
where: (predicate: unknown) => {
|
|
48
|
+
const done = Promise.resolve();
|
|
49
|
+
return Object.assign(done, {
|
|
50
|
+
returning: () => {
|
|
51
|
+
const [key, value] = boundParams(predicate);
|
|
52
|
+
const at = rows.findIndex((r) => r.key === key && r.value === value);
|
|
53
|
+
if (at === -1) return Promise.resolve([]);
|
|
54
|
+
rows.splice(at, 1);
|
|
55
|
+
return Promise.resolve([{ key }]);
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
}),
|
|
60
|
+
update: () => ({
|
|
61
|
+
set: (v: { value: string }) => ({
|
|
62
|
+
where: (predicate: unknown) => ({
|
|
63
|
+
returning: () => {
|
|
64
|
+
const [key, value] = boundParams(predicate);
|
|
65
|
+
const row = rows.find((r) => r.key === key && r.value === value);
|
|
66
|
+
if (!row) return Promise.resolve([]);
|
|
67
|
+
row.value = v.value;
|
|
68
|
+
return Promise.resolve([{ key }]);
|
|
69
|
+
},
|
|
70
|
+
}),
|
|
31
71
|
}),
|
|
32
72
|
}),
|
|
33
|
-
delete: () => ({ where: () => Promise.resolve() }),
|
|
34
73
|
} as unknown as Database;
|
|
35
74
|
return { db, rows };
|
|
36
75
|
}
|
|
37
76
|
|
|
77
|
+
/** The parameters a drizzle predicate binds, in order — what the fake table matches on. */
|
|
78
|
+
function boundParams(predicate: unknown): unknown[] {
|
|
79
|
+
return new PgDialect().sqlToQuery(predicate as SQL).params;
|
|
80
|
+
}
|
|
81
|
+
|
|
38
82
|
let saved: NodeJS.ProcessEnv;
|
|
39
83
|
beforeEach(() => {
|
|
40
84
|
saved = { ...process.env };
|
|
@@ -119,6 +163,56 @@ describe('DeploymentSettingsService — a blank that means the default', () => {
|
|
|
119
163
|
});
|
|
120
164
|
});
|
|
121
165
|
|
|
166
|
+
describe('DeploymentSettingsService — the leftover-branch cleanup switch', () => {
|
|
167
|
+
/**
|
|
168
|
+
* On by default; an admin switches it off on the Deployment page, and the
|
|
169
|
+
* environment variable wins over the page, as for every setting.
|
|
170
|
+
*/
|
|
171
|
+
it('is on by default, off once saved as false, and on again when cleared — after a reload too', async () => {
|
|
172
|
+
const { db, rows } = makeDb();
|
|
173
|
+
// Clearing deletes the row: the fake does so for real, so a reload proves it.
|
|
174
|
+
(db as unknown as { delete: () => unknown }).delete = () => ({
|
|
175
|
+
where: () => {
|
|
176
|
+
const at = rows.findIndex((r) => r.key === 'retireMergedBranches');
|
|
177
|
+
if (at >= 0) rows.splice(at, 1);
|
|
178
|
+
return Promise.resolve();
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
const settings = new DeploymentSettingsService(db, ENC_KEY);
|
|
182
|
+
const on = (s: DeploymentSettingsService) => retireMergedBranchesOn(s.resolve('retireMergedBranches'));
|
|
183
|
+
const reloaded = async () => {
|
|
184
|
+
const fresh = new DeploymentSettingsService(db, ENC_KEY);
|
|
185
|
+
await fresh.load();
|
|
186
|
+
return fresh;
|
|
187
|
+
};
|
|
188
|
+
expect(on(settings)).toBe(true);
|
|
189
|
+
await settings.save({ retireMergedBranches: 'false' }, null);
|
|
190
|
+
expect(on(settings)).toBe(false);
|
|
191
|
+
expect(on(await reloaded())).toBe(false);
|
|
192
|
+
await settings.save({ retireMergedBranches: '' }, null);
|
|
193
|
+
expect(on(settings)).toBe(true);
|
|
194
|
+
expect(rows.some((r) => r.key === 'retireMergedBranches')).toBe(false);
|
|
195
|
+
expect(on(await reloaded())).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('lets RETIRE_MERGED_BRANCHES win over the page', async () => {
|
|
199
|
+
const { db } = makeDb();
|
|
200
|
+
const settings = new DeploymentSettingsService(db, ENC_KEY);
|
|
201
|
+
await settings.save({ retireMergedBranches: 'true' }, null);
|
|
202
|
+
process.env.RETIRE_MERGED_BRANCHES = 'false';
|
|
203
|
+
expect(settings.sourceOf('retireMergedBranches')).toBe('env');
|
|
204
|
+
expect(retireMergedBranchesOn(settings.resolve('retireMergedBranches'))).toBe(false);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('saves only true or false, and reads the ways a person writes "off" as off', async () => {
|
|
208
|
+
const { db } = makeDb();
|
|
209
|
+
const settings = new DeploymentSettingsService(db, ENC_KEY);
|
|
210
|
+
await expect(settings.save({ retireMergedBranches: 'maybe' }, null)).rejects.toBeInstanceOf(SettingsValidationError);
|
|
211
|
+
for (const off of ['false', 'FALSE', '0', 'off', 'no']) expect(retireMergedBranchesOn(off), off).toBe(false);
|
|
212
|
+
for (const on of ['', 'true', '1', 'yes']) expect(retireMergedBranchesOn(on), on).toBe(true);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
122
216
|
describe('DeploymentSettingsService — secrets', () => {
|
|
123
217
|
it('stores the token as ciphertext and reads it back', async () => {
|
|
124
218
|
const { db, rows } = makeDb();
|
|
@@ -528,3 +622,58 @@ describe('DeploymentSettingsService — an environment of its own', () => {
|
|
|
528
622
|
expect(shared.resolve('kbRepoUrl')).toBe('https://host.example/leaks-into-every-tenant.git');
|
|
529
623
|
});
|
|
530
624
|
});
|
|
625
|
+
|
|
626
|
+
describe('DeploymentSettingsService — recordIfAbsent', () => {
|
|
627
|
+
/**
|
|
628
|
+
* The claim a once-only flow rests on: the row is the lock, and the
|
|
629
|
+
* database — shared by every replica — lets exactly one insert through.
|
|
630
|
+
*/
|
|
631
|
+
it('claims a setting once, and tells the second caller it was not theirs', async () => {
|
|
632
|
+
const { db, rows } = makeDb();
|
|
633
|
+
const one = new DeploymentSettingsService(db, ENC_KEY);
|
|
634
|
+
const two = new DeploymentSettingsService(db, ENC_KEY);
|
|
635
|
+
await one.load();
|
|
636
|
+
await two.load();
|
|
637
|
+
|
|
638
|
+
expect(await one.recordIfAbsent('starterPack', 'sales', 'u-ada')).toBe(true);
|
|
639
|
+
expect(await two.recordIfAbsent('starterPack', 'none', 'u-bo')).toBe(false);
|
|
640
|
+
|
|
641
|
+
expect(rows.filter((r) => r.key === 'starterPack')).toEqual([{ key: 'starterPack', value: 'sales', encrypted: false }]);
|
|
642
|
+
expect(one.resolve('starterPack')).toBe('sales');
|
|
643
|
+
// The loser's cache was not touched: it reads the winner's row on its next `reload`.
|
|
644
|
+
expect(two.resolve('starterPack')).toBe('');
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
it('is no claim at all with a blank value, which would be a clear', async () => {
|
|
648
|
+
const { db, rows } = makeDb();
|
|
649
|
+
const settings = new DeploymentSettingsService(db, ENC_KEY);
|
|
650
|
+
await settings.load();
|
|
651
|
+
expect(await settings.recordIfAbsent('starterPack', '', null)).toBe(false);
|
|
652
|
+
expect(rows).toEqual([]);
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* The conditional swap the claim flows rest on: a value is replaced, or
|
|
657
|
+
* removed, only while it still reads what the caller saw — so two
|
|
658
|
+
* replicas acting on one read cannot both succeed.
|
|
659
|
+
*/
|
|
660
|
+
it('swaps a value only while it still reads what was expected, and removes it the same way', async () => {
|
|
661
|
+
const { db, rows } = makeDb();
|
|
662
|
+
const settings = new DeploymentSettingsService(db, ENC_KEY);
|
|
663
|
+
await settings.load();
|
|
664
|
+
expect(await settings.recordIfAbsent('starterPackClaim', 'u-a 1', 'u-a')).toBe(true);
|
|
665
|
+
|
|
666
|
+
// The value moved under the second caller: refused, untouched.
|
|
667
|
+
expect(await settings.swapIfValue('starterPackClaim', 'u-z 0', 'u-b 2', 'u-b')).toBe(false);
|
|
668
|
+
expect(await settings.swapIfValue('starterPackClaim', 'u-a 1', 'u-b 2', 'u-b')).toBe(true);
|
|
669
|
+
expect(rows.find((r) => r.key === 'starterPackClaim')?.value).toBe('u-b 2');
|
|
670
|
+
expect(settings.resolve('starterPackClaim')).toBe('u-b 2');
|
|
671
|
+
|
|
672
|
+
// A release names its own claim: the holder's row stays for a stale one.
|
|
673
|
+
expect(await settings.swapIfValue('starterPackClaim', 'u-a 1', null, null)).toBe(false);
|
|
674
|
+
expect(rows.find((r) => r.key === 'starterPackClaim')?.value).toBe('u-b 2');
|
|
675
|
+
expect(await settings.swapIfValue('starterPackClaim', 'u-b 2', null, null)).toBe(true);
|
|
676
|
+
expect(rows.find((r) => r.key === 'starterPackClaim')).toBeUndefined();
|
|
677
|
+
expect(settings.resolve('starterPackClaim')).toBe('');
|
|
678
|
+
});
|
|
679
|
+
});
|
|
@@ -124,6 +124,15 @@ describe('RepositorySource: a repository reached through a GitHub App', () => {
|
|
|
124
124
|
expect(renewed).toBe(1);
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
+
it("hands the app the runner's word that the host refused the token", async () => {
|
|
128
|
+
const seen: unknown[] = [];
|
|
129
|
+
const githubApp = app({ prepare: async (opts) => void seen.push(opts) });
|
|
130
|
+
const { source: s } = source({ gitMode: 'github-app' }, { githubApp });
|
|
131
|
+
await s.credentials.prepare?.({ refused: true });
|
|
132
|
+
await s.credentials.prepare?.();
|
|
133
|
+
expect(seen).toEqual([{ refused: true }, undefined]);
|
|
134
|
+
});
|
|
135
|
+
|
|
127
136
|
it('is unanswered on a deployment that has no such app', () => {
|
|
128
137
|
const { source: s } = source({ gitMode: 'github-app' });
|
|
129
138
|
expect(s.answered()).toBe(false);
|