@bevel-software/platform-core-backend 0.33.0 → 0.35.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 +146 -289
- package/agent-guide/conventions.md +23 -25
- package/agent-guide/directory-structure.md +72 -120
- package/agent-guide/drafts-and-change-requests.md +92 -0
- package/agent-guide/for-git-users.md +47 -0
- package/agent-guide/glossary.md +36 -0
- package/agent-guide/how-agents-find-things.md +49 -0
- package/agent-guide/html-views.md +239 -8
- package/agent-guide/new-knowledge-base.md +9 -8
- package/agent-guide/skills.md +67 -42
- package/agent-guide/start-here.md +65 -0
- package/agent-guide/tool-manuals.md +133 -155
- package/agent-guide/tools.md +67 -0
- package/agent-guide/where-a-new-file-goes.md +45 -28
- package/dist/core/catalog-revision.d.ts +12 -1
- package/dist/core/catalog-revision.d.ts.map +1 -1
- package/dist/core/catalog-revision.js +23 -3
- package/dist/core/catalog-revision.js.map +1 -1
- package/dist/core/core-ports.d.ts +41 -0
- package/dist/core/core-ports.d.ts.map +1 -1
- package/dist/core/core-ports.js.map +1 -1
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +15 -8
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +22 -5
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +44 -16
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +16 -0
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +12 -2
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +39 -9
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +15 -3
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +239 -75
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access-mutation.service.d.ts +22 -24
- package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
- package/dist/modules/access/access-mutation.service.js +48 -51
- package/dist/modules/access/access-mutation.service.js.map +1 -1
- package/dist/modules/access/access-view.d.ts.map +1 -1
- package/dist/modules/access/access-view.js +12 -11
- package/dist/modules/access/access-view.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts +3 -0
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +58 -33
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/groups-admin.routes.d.ts.map +1 -1
- package/dist/modules/access/groups-admin.routes.js +4 -2
- package/dist/modules/access/groups-admin.routes.js.map +1 -1
- package/dist/modules/access/groups-admin.service.d.ts +44 -13
- package/dist/modules/access/groups-admin.service.d.ts.map +1 -1
- package/dist/modules/access/groups-admin.service.js +80 -38
- package/dist/modules/access/groups-admin.service.js.map +1 -1
- package/dist/modules/access/groups-edit.d.ts +1 -1
- package/dist/modules/access/groups-edit.d.ts.map +1 -1
- package/dist/modules/access/groups-edit.js +1 -1
- package/dist/modules/access/groups-edit.js.map +1 -1
- package/dist/modules/access/index.d.ts +1 -1
- package/dist/modules/access/index.d.ts.map +1 -1
- package/dist/modules/access/reference-scan.d.ts +116 -24
- package/dist/modules/access/reference-scan.d.ts.map +1 -1
- package/dist/modules/access/reference-scan.js +305 -44
- package/dist/modules/access/reference-scan.js.map +1 -1
- package/dist/modules/access/roles-admin.service.d.ts +17 -8
- package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
- package/dist/modules/access/roles-admin.service.js +32 -23
- package/dist/modules/access/roles-admin.service.js.map +1 -1
- package/dist/modules/access-model/access-grammar.d.ts +35 -10
- package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
- package/dist/modules/access-model/access-grammar.js +60 -20
- package/dist/modules/access-model/access-grammar.js.map +1 -1
- package/dist/modules/access-model/access-splice.d.ts +22 -18
- package/dist/modules/access-model/access-splice.d.ts.map +1 -1
- package/dist/modules/access-model/access-splice.js +20 -14
- package/dist/modules/access-model/access-splice.js.map +1 -1
- package/dist/modules/access-model/group-files.d.ts +1 -0
- package/dist/modules/access-model/group-files.d.ts.map +1 -1
- package/dist/modules/access-model/group-files.js +5 -1
- package/dist/modules/access-model/group-files.js.map +1 -1
- package/dist/modules/agent-guide/agent-guide.d.ts +47 -7
- package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.js +103 -13
- package/dist/modules/agent-guide/agent-guide.js.map +1 -1
- package/dist/modules/agent-guide/agent-guide.tools.d.ts +6 -2
- package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.tools.js +46 -22
- package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -1
- package/dist/modules/agent-guide/guide-budget.d.ts +48 -0
- package/dist/modules/agent-guide/guide-budget.d.ts.map +1 -0
- package/dist/modules/agent-guide/guide-budget.js +67 -0
- package/dist/modules/agent-guide/guide-budget.js.map +1 -0
- package/dist/modules/agent-guide/index.d.ts +2 -1
- package/dist/modules/agent-guide/index.d.ts.map +1 -1
- package/dist/modules/agent-guide/index.js +2 -1
- package/dist/modules/agent-guide/index.js.map +1 -1
- package/dist/modules/agent-instructions/compose.d.ts +3 -1
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
- package/dist/modules/agent-instructions/compose.js +7 -1
- package/dist/modules/agent-instructions/compose.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts +5 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.js +26 -9
- package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
- package/dist/modules/agent-scope/agent-scope.contract.d.ts +67 -0
- package/dist/modules/agent-scope/agent-scope.contract.d.ts.map +1 -0
- package/dist/modules/agent-scope/agent-scope.contract.js +27 -0
- package/dist/modules/agent-scope/agent-scope.contract.js.map +1 -0
- package/dist/modules/agent-scope/connection-scope.d.ts +111 -0
- package/dist/modules/agent-scope/connection-scope.d.ts.map +1 -0
- package/dist/modules/agent-scope/connection-scope.js +165 -0
- package/dist/modules/agent-scope/connection-scope.js.map +1 -0
- package/dist/modules/agent-scope/index.d.ts +3 -0
- package/dist/modules/agent-scope/index.d.ts.map +1 -0
- package/dist/modules/agent-scope/index.js +3 -0
- package/dist/modules/agent-scope/index.js.map +1 -0
- package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
- package/dist/modules/audit/agent-audit.service.js +2 -0
- package/dist/modules/audit/agent-audit.service.js.map +1 -1
- package/dist/modules/audit/audit.contract.d.ts +6 -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/auth/account-admission.d.ts +12 -0
- package/dist/modules/auth/account-admission.d.ts.map +1 -1
- package/dist/modules/auth/account-admission.js +15 -0
- package/dist/modules/auth/account-admission.js.map +1 -1
- package/dist/modules/auth/auth.middleware.d.ts +27 -1
- package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
- package/dist/modules/auth/auth.middleware.js +42 -4
- package/dist/modules/auth/auth.middleware.js.map +1 -1
- package/dist/modules/auth/auth.routes.d.ts +13 -9
- package/dist/modules/auth/auth.routes.d.ts.map +1 -1
- package/dist/modules/auth/auth.routes.js +35 -29
- package/dist/modules/auth/auth.routes.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +55 -6
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +128 -34
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/auth/index.d.ts +1 -1
- package/dist/modules/auth/index.d.ts.map +1 -1
- package/dist/modules/auth/index.js +1 -1
- package/dist/modules/auth/index.js.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.js +2 -9
- package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.js +2 -2
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.d.ts +0 -12
- package/dist/modules/connection-probe/connection-probe.service.d.ts.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.js +1 -38
- package/dist/modules/connection-probe/connection-probe.service.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +68 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +33 -0
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.d.ts +8 -1
- package/dist/modules/declared-variables/declared-variables.routes.d.ts.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.js +13 -2
- package/dist/modules/declared-variables/declared-variables.routes.js.map +1 -1
- package/dist/modules/embed/embed.tools.d.ts.map +1 -1
- package/dist/modules/embed/embed.tools.js +5 -0
- package/dist/modules/embed/embed.tools.js.map +1 -1
- package/dist/modules/kb-fs/index.d.ts +2 -0
- package/dist/modules/kb-fs/index.d.ts.map +1 -1
- package/dist/modules/kb-fs/index.js +1 -0
- package/dist/modules/kb-fs/index.js.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.js +7 -1
- package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
- package/dist/modules/kb-fs/repo-path.d.ts +26 -6
- package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
- package/dist/modules/kb-fs/repo-path.js +22 -7
- package/dist/modules/kb-fs/repo-path.js.map +1 -1
- package/dist/modules/kb-fs/tmp-ref.d.ts +36 -0
- package/dist/modules/kb-fs/tmp-ref.d.ts.map +1 -0
- package/dist/modules/kb-fs/tmp-ref.js +76 -0
- package/dist/modules/kb-fs/tmp-ref.js.map +1 -0
- package/dist/modules/mcp/mcp-auth.middleware.d.ts +7 -0
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +14 -1
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/mcp.routes.d.ts +7 -1
- package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.routes.js +39 -4
- package/dist/modules/mcp/mcp.routes.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +11 -0
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +74 -48
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +45 -10
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts +9 -0
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js +34 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
- package/dist/modules/plugins/index.d.ts +1 -1
- package/dist/modules/plugins/index.d.ts.map +1 -1
- package/dist/modules/plugins/index.js.map +1 -1
- package/dist/modules/plugins/plugin-links.d.ts +18 -2
- package/dist/modules/plugins/plugin-links.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-links.js.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +5 -1
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +32 -5
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +11 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.js +159 -24
- package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
- package/dist/modules/secrets-vault/index.d.ts +1 -1
- package/dist/modules/secrets-vault/index.d.ts.map +1 -1
- package/dist/modules/secrets-vault/index.js +1 -1
- package/dist/modules/secrets-vault/index.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +53 -11
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.js +27 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +9 -0
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.js +100 -42
- package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
- package/dist/modules/settings/oidc-check.d.ts.map +1 -1
- package/dist/modules/settings/oidc-check.js +5 -17
- package/dist/modules/settings/oidc-check.js.map +1 -1
- package/dist/modules/skills/index.d.ts +3 -3
- package/dist/modules/skills/index.d.ts.map +1 -1
- package/dist/modules/skills/index.js +0 -2
- package/dist/modules/skills/index.js.map +1 -1
- package/dist/modules/skills/skills.contract.d.ts +16 -0
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +6 -1
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +25 -13
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.d.ts +11 -0
- package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.d.ts +1 -0
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +4 -1
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/index.d.ts +1 -1
- package/dist/modules/tool-auth/index.d.ts.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.d.ts +9 -0
- package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.js +1 -0
- package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts +27 -3
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +32 -6
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/tool-context.d.ts +7 -0
- package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-context.js +3 -0
- package/dist/modules/tool-helpers/tool-context.js.map +1 -1
- package/dist/modules/tool-helpers/tool-def.js +1 -1
- package/dist/modules/tool-helpers/tool-def.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts +15 -0
- package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.js +19 -0
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts +10 -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-helpers/validate-token.d.ts +3 -1
- package/dist/modules/tool-helpers/validate-token.d.ts.map +1 -1
- package/dist/modules/tool-helpers/validate-token.js +1 -1
- package/dist/modules/tool-helpers/validate-token.js.map +1 -1
- package/dist/modules/tool-manuals/index.d.ts +3 -3
- package/dist/modules/tool-manuals/index.d.ts.map +1 -1
- package/dist/modules/tool-manuals/index.js +0 -2
- package/dist/modules/tool-manuals/index.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts +7 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.js +69 -38
- package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +46 -6
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +7 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.js +41 -4
- package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +24 -13
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +138 -59
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +8 -2
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +56 -20
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/tool-registry/manual.routes.d.ts +8 -1
- package/dist/modules/tool-registry/manual.routes.d.ts.map +1 -1
- package/dist/modules/tool-registry/manual.routes.js +27 -7
- package/dist/modules/tool-registry/manual.routes.js.map +1 -1
- package/dist/modules/tool-registry/tool.contract.d.ts +7 -0
- package/dist/modules/tool-registry/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-registry/tool.contract.js.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +36 -27
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +2 -2
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +20 -35
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +2 -1
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +3 -0
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/agent-access.gate.d.ts +1 -1
- package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -1
- package/dist/modules/workspace/agent-access.gate.js +1 -1
- package/dist/modules/workspace/agent-access.gate.js.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts +28 -4
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.js +61 -40
- package/dist/modules/workspace/file-readers/doc-extract.service.js.map +1 -1
- package/dist/modules/workspace/index.d.ts +8 -0
- package/dist/modules/workspace/index.d.ts.map +1 -1
- package/dist/modules/workspace/index.js +8 -0
- package/dist/modules/workspace/index.js.map +1 -1
- package/dist/modules/workspace/nul-scan.d.ts +19 -0
- package/dist/modules/workspace/nul-scan.d.ts.map +1 -0
- package/dist/modules/workspace/nul-scan.js +55 -0
- package/dist/modules/workspace/nul-scan.js.map +1 -0
- package/dist/modules/workspace/pattern-matcher.contract.d.ts +110 -27
- package/dist/modules/workspace/pattern-matcher.contract.d.ts.map +1 -1
- package/dist/modules/workspace/pattern-matcher.contract.js +30 -13
- package/dist/modules/workspace/pattern-matcher.contract.js.map +1 -1
- package/dist/modules/workspace/ripgrep-search.d.ts +54 -0
- package/dist/modules/workspace/ripgrep-search.d.ts.map +1 -0
- package/dist/modules/workspace/ripgrep-search.js +657 -0
- package/dist/modules/workspace/ripgrep-search.js.map +1 -0
- package/dist/modules/workspace/ripgrep-version.d.ts +15 -0
- package/dist/modules/workspace/ripgrep-version.d.ts.map +1 -0
- package/dist/modules/workspace/ripgrep-version.js +31 -0
- package/dist/modules/workspace/ripgrep-version.js.map +1 -0
- package/dist/modules/workspace/startup/beside-checkout.d.ts +8 -5
- package/dist/modules/workspace/startup/beside-checkout.d.ts.map +1 -1
- package/dist/modules/workspace/startup/beside-checkout.js +14 -5
- package/dist/modules/workspace/startup/beside-checkout.js.map +1 -1
- package/dist/modules/workspace/worker-pool.d.ts +3 -4
- package/dist/modules/workspace/worker-pool.d.ts.map +1 -1
- package/dist/modules/workspace/worker-pool.js.map +1 -1
- package/dist/modules/workspace/workspace-tmp.d.ts +146 -0
- package/dist/modules/workspace/workspace-tmp.d.ts.map +1 -0
- package/dist/modules/workspace/workspace-tmp.js +435 -0
- package/dist/modules/workspace/workspace-tmp.js.map +1 -0
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +5 -26
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +47 -6
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +86 -6
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +3 -2
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +531 -137
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/shared/oauth-token-error.d.ts +23 -0
- package/dist/shared/oauth-token-error.d.ts.map +1 -0
- package/dist/shared/oauth-token-error.js +47 -0
- package/dist/shared/oauth-token-error.js.map +1 -0
- package/dist/shared/path-order.d.ts +28 -8
- package/dist/shared/path-order.d.ts.map +1 -1
- package/dist/shared/path-order.js +37 -8
- package/dist/shared/path-order.js.map +1 -1
- package/dist/shared/read-capped.d.ts +19 -0
- package/dist/shared/read-capped.d.ts.map +1 -0
- package/dist/shared/read-capped.js +45 -0
- package/dist/shared/read-capped.js.map +1 -0
- package/migrations/0022_session_version.sql +6 -0
- package/migrations/0023_agent_path.sql +3 -0
- package/migrations/meta/0022_snapshot.json +2360 -0
- package/migrations/meta/0023_snapshot.json +2378 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +4 -3
- package/src/__tests__/hexis-catalog.ts +144 -0
- package/src/__tests__/pg-scratch.test.ts +69 -0
- package/src/__tests__/pg-scratch.ts +31 -0
- package/src/__tests__/real-disk.ts +21 -0
- package/src/__tests__/slow-ripgrep.ts +65 -0
- package/src/__tests__/tokens.ts +19 -0
- package/src/__tests__/tool-manuals.ts +32 -0
- package/src/__tests__/tool-wiring.ts +25 -0
- package/src/core/__tests__/boot-without-ripgrep.test.ts +34 -0
- package/src/core/__tests__/catalog-revision.test.ts +89 -0
- package/src/core/__tests__/damaged-document.tools.test.ts +2 -2
- package/src/core/__tests__/gated-boot.test.ts +8 -1
- package/src/core/__tests__/lifecycle.test.ts +46 -13
- package/src/core/__tests__/oauth-sign-in-real-file.test.ts +179 -0
- package/src/core/catalog-revision.ts +24 -2
- package/src/core/core-ports.ts +42 -0
- package/src/core/create-core-server.ts +14 -1
- package/src/core/create-core-services.ts +62 -15
- package/src/core/lifecycle.ts +26 -2
- package/src/index.ts +1 -1
- package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +12 -0
- package/src/modules/access/__tests__/access-control.colliding-name-cases.test.ts +113 -0
- package/src/modules/access/__tests__/access-control.frontmatter-head.test.ts +117 -0
- package/src/modules/access/__tests__/access-control.group-named-admin.test.ts +122 -0
- package/src/modules/access/__tests__/access-control.preview-relocation.test.ts +50 -0
- package/src/modules/access/__tests__/access-declarations.test.ts +19 -0
- package/src/modules/access/__tests__/access-groups.test.ts +55 -22
- package/src/modules/access/__tests__/access-mutation.service.test.ts +85 -59
- package/src/modules/access/__tests__/access-restrict-here.test.ts +7 -5
- package/src/modules/access/__tests__/access.routes.admins.test.ts +1 -0
- package/src/modules/access/__tests__/access.routes.batch.test.ts +2 -0
- package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +2 -0
- package/src/modules/access/__tests__/access.routes.group-grant.test.ts +50 -14
- package/src/modules/access/__tests__/access.routes.overrides.test.ts +2 -1
- package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +1 -0
- package/src/modules/access/__tests__/access.routes.prospective.test.ts +52 -10
- package/src/modules/access/__tests__/access.routes.revoke.test.ts +18 -13
- package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +2 -0
- package/src/modules/access/__tests__/grant-sources.test.ts +20 -15
- package/src/modules/access/__tests__/groups-admin.routes.test.ts +61 -0
- package/src/modules/access/__tests__/groups-admin.service.test.ts +211 -18
- package/src/modules/access/__tests__/holder-changes.test.ts +127 -0
- package/src/modules/access/__tests__/reference-scan.test.ts +366 -14
- package/src/modules/access/__tests__/roles-admin.security.test.ts +1 -1
- package/src/modules/access/__tests__/roles-admin.service.test.ts +43 -22
- package/src/modules/access/__tests__/roles-capabilities.test.ts +6 -2
- package/src/modules/access/__tests__/roles.routes.test.ts +1 -1
- package/src/modules/access/access-control.interface.ts +43 -10
- package/src/modules/access/access-control.service.ts +250 -72
- package/src/modules/access/access-mutation.service.ts +48 -53
- package/src/modules/access/access-view.ts +12 -10
- package/src/modules/access/access.routes.ts +66 -37
- package/src/modules/access/groups-admin.routes.ts +4 -2
- package/src/modules/access/groups-admin.service.ts +114 -44
- package/src/modules/access/groups-edit.ts +1 -1
- package/src/modules/access/index.ts +1 -1
- package/src/modules/access/reference-scan.ts +319 -46
- package/src/modules/access/roles-admin.service.ts +43 -26
- package/src/modules/access/synced-groups-writer.ts +1 -1
- package/src/modules/access-model/__tests__/access-grammar.test.ts +38 -1
- package/src/modules/access-model/__tests__/access-splice.test.ts +50 -8
- package/src/modules/access-model/access-grammar.ts +65 -20
- package/src/modules/access-model/access-splice.ts +31 -23
- package/src/modules/access-model/group-files.ts +5 -0
- package/src/modules/agent-guide/__tests__/agent-guide.test.ts +110 -25
- package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +38 -11
- package/src/modules/agent-guide/__tests__/guide-budget.test.ts +95 -0
- package/src/modules/agent-guide/__tests__/guide-eval.test.ts +182 -0
- package/src/modules/agent-guide/__tests__/guide-matches-code.test.ts +263 -0
- package/src/modules/agent-guide/__tests__/tool-surface.json +514 -0
- package/src/modules/agent-guide/__tests__/tool-surface.test.ts +75 -0
- package/src/modules/agent-guide/agent-guide.tools.ts +51 -22
- package/src/modules/agent-guide/agent-guide.ts +120 -13
- package/src/modules/agent-guide/guide-budget.ts +73 -0
- package/src/modules/agent-guide/index.ts +12 -0
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +5 -4
- package/src/modules/agent-instructions/compose.ts +7 -1
- package/src/modules/agent-instructions/shared-file-rules.ts +27 -9
- package/src/modules/agent-scope/__tests__/agent-surfaces.connect-as.test.ts +421 -0
- package/src/modules/agent-scope/__tests__/connection-scope.test.ts +46 -0
- package/src/modules/agent-scope/__tests__/in-memory-kb.ts +148 -0
- package/src/modules/agent-scope/agent-scope.contract.ts +83 -0
- package/src/modules/agent-scope/connection-scope.ts +199 -0
- package/src/modules/agent-scope/index.ts +15 -0
- package/src/modules/audit/agent-audit.service.ts +2 -0
- package/src/modules/audit/audit.contract.ts +6 -0
- package/src/modules/auth/__tests__/auth.middleware.test.ts +30 -2
- package/src/modules/auth/__tests__/auth.routes.test.ts +71 -5
- package/src/modules/auth/__tests__/auth.service.test.ts +90 -9
- package/src/modules/auth/__tests__/password-change-sessions.pg.test.ts +269 -0
- package/src/modules/auth/account-admission.ts +19 -0
- package/src/modules/auth/auth.middleware.ts +45 -4
- package/src/modules/auth/auth.routes.ts +46 -29
- package/src/modules/auth/auth.service.ts +135 -34
- package/src/modules/auth/index.ts +2 -0
- package/src/modules/auth/oidc-auth-provider.ts +2 -9
- package/src/modules/code-mode/code-mode.tool.ts +2 -2
- package/src/modules/connection-probe/__tests__/connection-probe.service.test.ts +5 -24
- package/src/modules/connection-probe/connection-probe.service.ts +1 -34
- package/src/modules/database/__tests__/session-version-migration.pg.test.ts +133 -0
- package/src/modules/database/core-schema.ts +33 -0
- package/src/modules/declared-variables/__tests__/declared-variables.route.test.ts +44 -1
- package/src/modules/declared-variables/declared-variables.routes.ts +12 -1
- package/src/modules/embed/__tests__/embed.tools.test.ts +12 -0
- package/src/modules/embed/embed.tools.ts +5 -0
- package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +25 -0
- package/src/modules/kb-fs/__tests__/repo-path.test.ts +53 -8
- package/src/modules/kb-fs/index.ts +2 -0
- package/src/modules/kb-fs/locking-filesystem.ts +7 -1
- package/src/modules/kb-fs/repo-path.ts +33 -7
- package/src/modules/kb-fs/tmp-ref.ts +81 -0
- package/src/modules/mcp/__tests__/connect-as.pg.test.ts +137 -0
- package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +29 -5
- package/src/modules/mcp/__tests__/mcp-routes-harness.ts +8 -0
- package/src/modules/mcp/__tests__/mcp.connect-as.test.ts +195 -0
- package/src/modules/mcp/__tests__/mcp.routes.connect-as.test.ts +192 -0
- package/src/modules/mcp/__tests__/oauth-consent.connect-as.test.ts +146 -0
- package/src/modules/mcp/mcp-auth.middleware.ts +19 -2
- package/src/modules/mcp/mcp.routes.ts +39 -3
- package/src/modules/mcp/mcp.service.ts +112 -52
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +46 -8
- package/src/modules/mcp/oauth/oauth-consent.routes.ts +41 -1
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +38 -2
- package/src/modules/plugins/index.ts +1 -1
- package/src/modules/plugins/plugin-links.ts +19 -2
- package/src/modules/plugins/plugins.routes.ts +35 -4
- package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +113 -0
- package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +430 -3
- package/src/modules/secrets-vault/__tests__/oauth-live-declaration.route.test.ts +218 -0
- package/src/modules/secrets-vault/__tests__/oauth-return-to.route.test.ts +39 -6
- package/src/modules/secrets-vault/db-secrets-vault.service.ts +170 -27
- package/src/modules/secrets-vault/index.ts +1 -1
- package/src/modules/secrets-vault/secrets-vault.contract.ts +61 -11
- package/src/modules/secrets-vault/secrets-vault.routes.ts +114 -42
- package/src/modules/settings/oidc-check.ts +5 -18
- package/src/modules/skills/__tests__/skills.service.test.ts +34 -0
- package/src/modules/skills/index.ts +7 -2
- package/src/modules/skills/skills.contract.ts +17 -0
- package/src/modules/skills/skills.service.ts +5 -1
- package/src/modules/skills/skills.tools.ts +45 -14
- package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +4 -0
- package/src/modules/tool-auth/external-api-key.interface.ts +11 -1
- package/src/modules/tool-auth/external-api-key.service.ts +5 -2
- package/src/modules/tool-auth/index.ts +1 -1
- package/src/modules/tool-auth/internal-token.service.ts +10 -0
- package/src/modules/tool-auth/tool-auth.middleware.ts +45 -3
- package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +19 -11
- package/src/modules/tool-helpers/__tests__/can-write.tools.test.ts +3 -1
- package/src/modules/tool-helpers/__tests__/route-tool-schemas.catalog.test.ts +38 -0
- package/src/modules/tool-helpers/tool-context.ts +10 -0
- package/src/modules/tool-helpers/tool-def.ts +1 -1
- package/src/modules/tool-helpers/tool-handler.ts +34 -0
- package/src/modules/tool-helpers/tool.contract.ts +10 -0
- package/src/modules/tool-helpers/validate-token.ts +4 -2
- package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +80 -4
- package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +197 -11
- package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +42 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +99 -8
- package/src/modules/tool-manuals/index.ts +6 -2
- package/src/modules/tool-manuals/mcp-json-discovery.ts +73 -34
- package/src/modules/tool-manuals/tool-manuals.contract.ts +40 -6
- package/src/modules/tool-manuals/tool-manuals.routes.ts +40 -3
- package/src/modules/tool-manuals/tool-manuals.service.ts +169 -61
- package/src/modules/tool-manuals/tool-manuals.tools.ts +81 -25
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +6 -113
- package/src/modules/tool-registry/manual.routes.ts +26 -5
- package/src/modules/tool-registry/tool.contract.ts +7 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +1 -1
- package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +1 -1
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +54 -3
- package/src/modules/workflow/agent-tools/workflow.tools.ts +36 -27
- package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +130 -3
- package/src/modules/workflow/git/git.service.ts +29 -48
- package/src/modules/workflow/workflow.service.ts +5 -0
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +2 -2
- package/src/modules/workspace/__tests__/holds-nul.test.ts +66 -0
- package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +3 -3
- package/src/modules/workspace/__tests__/ripgrep-search.test.ts +570 -0
- package/src/modules/workspace/__tests__/ripgrep-version.test.ts +29 -0
- package/src/modules/workspace/__tests__/workspace-tmp.tools.test.ts +928 -0
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +17 -37
- package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +20 -8
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +10 -12
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +22 -10
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +549 -162
- package/src/modules/workspace/agent-access.gate.ts +1 -1
- package/src/modules/workspace/file-readers/__tests__/doc-extract.service.test.ts +10 -0
- package/src/modules/workspace/file-readers/doc-extract.service.ts +71 -38
- package/src/modules/workspace/index.ts +20 -0
- package/src/modules/workspace/nul-scan.ts +50 -0
- package/src/modules/workspace/pattern-matcher.contract.ts +123 -32
- package/src/modules/workspace/ripgrep-search.ts +715 -0
- package/src/modules/workspace/ripgrep-version.ts +35 -0
- package/src/modules/workspace/startup/__tests__/beside-checkout.test.ts +11 -0
- package/src/modules/workspace/startup/beside-checkout.ts +13 -5
- package/src/modules/workspace/worker-pool.ts +3 -4
- package/src/modules/workspace/workspace-tmp.ts +457 -0
- package/src/modules/workspace/workspace.routes.ts +6 -27
- package/src/modules/workspace/workspace.service.ts +92 -15
- package/src/modules/workspace/workspace.tools.ts +574 -176
- package/src/shared/__tests__/read-capped.test.ts +68 -0
- package/src/shared/oauth-token-error.ts +46 -0
- package/src/shared/path-order.ts +37 -8
- package/src/shared/read-capped.ts +40 -0
- package/starter-packs/engineering/Plugins/engineering-starter/access.md +1 -1
- package/starter-packs/general/Plugins/general-starter/access.md +1 -1
- package/starter-packs/operations/Plugins/operations-starter/access.md +1 -1
- package/starter-packs/sales/Plugins/sales-starter/access.md +1 -1
- package/starter-packs/support/Plugins/support-starter/access.md +1 -1
- package/agent-guide/finding-things.md +0 -7
- package/agent-guide/introduction.md +0 -27
- package/dist/modules/workspace/bounded-matcher.d.ts +0 -73
- package/dist/modules/workspace/bounded-matcher.d.ts.map +0 -1
- package/dist/modules/workspace/bounded-matcher.js +0 -200
- package/dist/modules/workspace/bounded-matcher.js.map +0 -1
- package/src/modules/workspace/__tests__/bounded-matcher.test.ts +0 -213
- package/src/modules/workspace/bounded-matcher.ts +0 -271
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { ConnectionScope } from './connection-scope.js';
|
|
2
|
+
|
|
3
|
+
/** One agent configuration a person may connect as — a row of the "Connect as" choice. */
|
|
4
|
+
export interface AgentChoice {
|
|
5
|
+
/** Repo-relative path of the configuration — what the connection records. */
|
|
6
|
+
path: string;
|
|
7
|
+
/** Its name, as the choice shows it. */
|
|
8
|
+
name: string;
|
|
9
|
+
/** Its description, one line; empty when it has none. */
|
|
10
|
+
description: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The connection-scope mechanism: which agent configurations a person may
|
|
15
|
+
* connect as, and what a connection made as one may load right now.
|
|
16
|
+
*
|
|
17
|
+
* Core owns the MECHANISM and none of the configurations: what an agent
|
|
18
|
+
* configuration is — the file kind, where it lives, what its definition
|
|
19
|
+
* says — is the distribution's (`CorePorts.agentScope`), exactly as the
|
|
20
|
+
* file kinds whose frontmatter carries access verbs are
|
|
21
|
+
* (`registerAccessFrontmatterExtensions`). Core itself offers no agent to
|
|
22
|
+
* connect as ({@link noAgentConfigurations}): every connection it makes is
|
|
23
|
+
* Everything, and a connection recorded as some path by a distribution that
|
|
24
|
+
* is no longer there loads nothing, never Everything.
|
|
25
|
+
*
|
|
26
|
+
* Every agent surface resolves its caller's scope here, with the agent path
|
|
27
|
+
* its credential carries — a connection key's `api_tokens.agent_path`, an
|
|
28
|
+
* agent connection's `agent_connections.agent_path` — and asks the scope;
|
|
29
|
+
* none reads a configuration itself.
|
|
30
|
+
*/
|
|
31
|
+
export interface IAgentScopeResolver {
|
|
32
|
+
/**
|
|
33
|
+
* The scope of a connection made as `agentPath` for the person `userEmail`.
|
|
34
|
+
* No path is Everything. The configuration is read, through the person's
|
|
35
|
+
* read gate, on EVERY call — the connection follows the configuration, not
|
|
36
|
+
* a copy of it — and one that is missing, unreadable to the person or not
|
|
37
|
+
* a valid definition yields a scope that loads nothing, never Everything.
|
|
38
|
+
*/
|
|
39
|
+
resolve(userEmail: string | undefined, agentPath: string | null | undefined): Promise<ConnectionScope>;
|
|
40
|
+
/**
|
|
41
|
+
* The agent configurations `userEmail` may connect as, by name — what the
|
|
42
|
+
* consent step and the key-mint dialog offer below Everything. Rejects
|
|
43
|
+
* when they cannot be listed at all (the default branch cannot be opened):
|
|
44
|
+
* that is a failed listing, which the caller says, never an empty one.
|
|
45
|
+
*/
|
|
46
|
+
listChoices(userEmail: string): Promise<AgentChoice[]>;
|
|
47
|
+
/**
|
|
48
|
+
* `agentPath` normalized when it is one of {@link listChoices}' paths, else
|
|
49
|
+
* null — one answer for a configuration that does not exist and one the
|
|
50
|
+
* person may not read, so a refused choice says nothing about either.
|
|
51
|
+
*/
|
|
52
|
+
choosable(userEmail: string, agentPath: string): Promise<string | null>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The resolver of a distribution that has no agent configurations — core's
|
|
57
|
+
* own default. Nothing is offered, nothing is choosable, and a recorded path
|
|
58
|
+
* resolves to a scope that loads nothing: a narrowing that cannot be read is
|
|
59
|
+
* never read as Everything.
|
|
60
|
+
*/
|
|
61
|
+
export function noAgentConfigurations(): IAgentScopeResolver {
|
|
62
|
+
return {
|
|
63
|
+
resolve: async (_userEmail, agentPath) => (agentPath ? ConnectionScope.gone(agentPath) : ConnectionScope.everything()),
|
|
64
|
+
listChoices: async () => [],
|
|
65
|
+
choosable: async () => null,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The scope of a caller whose credential connects as `agentPath` — THE way
|
|
71
|
+
* every agent surface turns a credential into a scope. No path is
|
|
72
|
+
* Everything, without asking anything. A path with no resolver to read it
|
|
73
|
+
* (a construction that wired none) loads nothing: a narrowing that cannot be
|
|
74
|
+
* read is never read as Everything.
|
|
75
|
+
*/
|
|
76
|
+
export async function resolveConnectionScope(
|
|
77
|
+
resolver: Pick<IAgentScopeResolver, 'resolve'> | undefined,
|
|
78
|
+
userEmail: string | undefined,
|
|
79
|
+
agentPath: string | null | undefined,
|
|
80
|
+
): Promise<ConnectionScope> {
|
|
81
|
+
if (!agentPath) return ConnectionScope.everything();
|
|
82
|
+
return resolver ? resolver.resolve(userEmail, agentPath) : ConnectionScope.gone(agentPath);
|
|
83
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { pathUnderRoot } from '@bevel-software/platform-shared';
|
|
2
|
+
import {
|
|
3
|
+
NOT_AVAILABLE_TO_CONNECTION,
|
|
4
|
+
connectionRefusal,
|
|
5
|
+
describeConnection,
|
|
6
|
+
type ConnectionScopeWire,
|
|
7
|
+
type ScopeAgent,
|
|
8
|
+
} from '@bevel-software/platform-mcp-core';
|
|
9
|
+
|
|
10
|
+
// The wire and its words are the MCP core's (`connection-scope-wire.ts`),
|
|
11
|
+
// so the hosted proxy and the local server read and word a connection the
|
|
12
|
+
// same way; offered here too, as this module's callers learnt them.
|
|
13
|
+
export { NOT_AVAILABLE_TO_CONNECTION, connectionRefusal, describeConnection, type ConnectionScopeWire, type ScopeAgent };
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The resolved roots a usable agent configuration narrows a connection to — what
|
|
17
|
+
* {@link ConnectionScope.narrowed} is built from.
|
|
18
|
+
*/
|
|
19
|
+
export interface ScopeRoots {
|
|
20
|
+
/** Skill folders and subtrees: the `skills:` entries, each plugin folder, and the skills its manifest links. */
|
|
21
|
+
skills: readonly string[];
|
|
22
|
+
/** Connector files and subtrees: the `connectors:` entries and each plugin folder. */
|
|
23
|
+
connectors: readonly string[];
|
|
24
|
+
/** Platform tool names; null for all of them. */
|
|
25
|
+
platformTools: readonly string[] | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type State =
|
|
29
|
+
| { kind: 'everything' }
|
|
30
|
+
| { kind: 'narrowed'; agent: ScopeAgent; roots: ScopeRoots; fingerprint: string }
|
|
31
|
+
| { kind: 'unavailable'; agent: ScopeAgent; sentence: string; fingerprint: string };
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* What one connection may load — the ONE answer every agent surface asks
|
|
35
|
+
* (`all-tools`, the `/agent/utcp` manual, the tool route, the skill tools,
|
|
36
|
+
* the setup tools, the catalog revision). Everything is the scope of every
|
|
37
|
+
* connection made without a choice, and asks nothing of anyone: each of its
|
|
38
|
+
* predicates is true, so a surface that consults it serves exactly what it
|
|
39
|
+
* served before connections could be narrowed.
|
|
40
|
+
*
|
|
41
|
+
* A scope only ever NARROWS. It is applied to what the person may already
|
|
42
|
+
* read — each surface asks it about items that passed the person's own read
|
|
43
|
+
* gate — so no list in an agent configuration can add an item to a connection.
|
|
44
|
+
*/
|
|
45
|
+
export class ConnectionScope {
|
|
46
|
+
private constructor(private readonly state: State) {}
|
|
47
|
+
|
|
48
|
+
static everything(): ConnectionScope {
|
|
49
|
+
return new ConnectionScope({ kind: 'everything' });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static narrowed(agent: ScopeAgent, roots: ScopeRoots, fingerprint: string): ConnectionScope {
|
|
53
|
+
return new ConnectionScope({ kind: 'narrowed', agent, roots, fingerprint });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** A chosen file that cannot be used: nothing at all is loaded, and `sentence` says why. */
|
|
57
|
+
static unavailable(agent: ScopeAgent, sentence: string, fingerprint: string): ConnectionScope {
|
|
58
|
+
return new ConnectionScope({ kind: 'unavailable', agent, sentence, fingerprint });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* A chosen file that is not there for the person — missing, or one they may
|
|
63
|
+
* no longer read: ONE answer for both, so the sentence discloses neither.
|
|
64
|
+
*/
|
|
65
|
+
static gone(agentPath: string): ConnectionScope {
|
|
66
|
+
return ConnectionScope.unavailable(
|
|
67
|
+
{ path: agentPath, name: agentPath },
|
|
68
|
+
`This connection was made as the agent \`${agentPath}\`, which no longer exists or which you can no ` +
|
|
69
|
+
'longer read, so it loads no tools and no skills. Reconnect and choose another agent, or Everything.',
|
|
70
|
+
`agent\u0000${agentPath}\u0000gone`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** True for Everything: the connection was made without choosing an agent. */
|
|
75
|
+
isEverything(): boolean {
|
|
76
|
+
return this.state.kind === 'everything';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Whether the skill in `skillFolder` (repo-relative) is loaded. */
|
|
80
|
+
allowsSkill(skillFolder: string): boolean {
|
|
81
|
+
const s = this.state;
|
|
82
|
+
if (s.kind === 'everything') return true;
|
|
83
|
+
if (s.kind === 'unavailable') return false;
|
|
84
|
+
return s.roots.skills.some((root) => pathUnderRoot(skillFolder, root));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Whether the connector defined at `definitionPath` (a `.tool` file or an MCP server definition) is loaded. */
|
|
88
|
+
allowsConnector(definitionPath: string): boolean {
|
|
89
|
+
const s = this.state;
|
|
90
|
+
if (s.kind === 'everything') return true;
|
|
91
|
+
if (s.kind === 'unavailable') return false;
|
|
92
|
+
return s.roots.connectors.some((root) => pathUnderRoot(definitionPath, root));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Whether any connector this connection loads is defined in `folder`
|
|
97
|
+
* (repo-relative) or in a folder that contains it — what a plugin's
|
|
98
|
+
* archive asks, since the local MCP server materializes a plugin only to
|
|
99
|
+
* run a connector it defines.
|
|
100
|
+
*/
|
|
101
|
+
loadsConnectorIn(folder: string): boolean {
|
|
102
|
+
const s = this.state;
|
|
103
|
+
if (s.kind === 'everything') return true;
|
|
104
|
+
if (s.kind === 'unavailable') return false;
|
|
105
|
+
return s.roots.connectors.some((root) => pathUnderRoot(folder, root) || pathUnderRoot(root, folder));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* {@link allowsSkill} as the narrowing a skill listing takes, or undefined
|
|
110
|
+
* for Everything — so an Everything caller's listing runs exactly the code
|
|
111
|
+
* it ran before connections could be narrowed.
|
|
112
|
+
*/
|
|
113
|
+
skillFilter(): ((skillFolder: string) => boolean) | undefined {
|
|
114
|
+
return this.state.kind === 'everything' ? undefined : (p) => this.allowsSkill(p);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** {@link allowsConnector} as the narrowing a catalog read takes, or undefined for Everything. */
|
|
118
|
+
connectorFilter(): ((definitionPath: string) => boolean) | undefined {
|
|
119
|
+
return this.state.kind === 'everything' ? undefined : (p) => this.allowsConnector(p);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Whether the connection's surface may be discovered: its skills read as
|
|
124
|
+
* MCP prompts, and its local-only connectors read by the local server so
|
|
125
|
+
* it can materialize them. Neither is a platform tool, so `platformTools`
|
|
126
|
+
* has no say — an agent that names skills gets them as prompts, and one
|
|
127
|
+
* that names connectors gets them served, whatever tools it is offered.
|
|
128
|
+
* False only for a file that cannot be used, which loads nothing at all.
|
|
129
|
+
*/
|
|
130
|
+
allowsDiscovery(): boolean {
|
|
131
|
+
return this.state.kind !== 'unavailable';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Whether the platform's own tool `name` is loaded. */
|
|
135
|
+
allowsPlatformTool(name: string): boolean {
|
|
136
|
+
const s = this.state;
|
|
137
|
+
if (s.kind === 'everything') return true;
|
|
138
|
+
if (s.kind === 'unavailable') return false;
|
|
139
|
+
return s.roots.platformTools === null || s.roots.platformTools.includes(name);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The answer to a call of `toolName`, a tool this connection does not load:
|
|
144
|
+
* names the tool and the agent, so the caller knows the tool was refused by
|
|
145
|
+
* the connection's choice and not missing from the deployment. For a file
|
|
146
|
+
* that cannot be used, the sentence that says why.
|
|
147
|
+
*/
|
|
148
|
+
refusal(toolName: string): string {
|
|
149
|
+
return connectionRefusal(toolName, this.toWire());
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* THE policy for a call of the platform tool `toolName` by this connection:
|
|
154
|
+
* null when the call may proceed, else the body the route answers with a
|
|
155
|
+
* 403 — the refusal naming the tool and the agent, and the code that says
|
|
156
|
+
* it was the connection's choice. Every route that IS a platform tool asks
|
|
157
|
+
* this, whether `toolHandler` hosts it or it is mounted on its own (the
|
|
158
|
+
* plugin-creation routes), so the rule is written once and the answers
|
|
159
|
+
* cannot drift. `discovery` marks a route that is a discovery read of the
|
|
160
|
+
* surface (the skill routes, the local-only tool list), which a narrowed
|
|
161
|
+
* connection is answered even when its `platformTools` omits the tool;
|
|
162
|
+
* a connection that loads nothing is refused it like everything else.
|
|
163
|
+
*/
|
|
164
|
+
platformToolRefusal(
|
|
165
|
+
toolName: string,
|
|
166
|
+
opts: { discovery?: boolean } = {},
|
|
167
|
+
): { error: string; code: typeof NOT_AVAILABLE_TO_CONNECTION } | null {
|
|
168
|
+
if (this.allowsPlatformTool(toolName)) return null;
|
|
169
|
+
if (opts.discovery === true && this.allowsDiscovery()) return null;
|
|
170
|
+
return { error: this.refusal(toolName), code: NOT_AVAILABLE_TO_CONNECTION };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* One line for the text an agent is told about its connection (the MCP
|
|
175
|
+
* handshake, the agent guide); null for Everything, which is told nothing
|
|
176
|
+
* new.
|
|
177
|
+
*/
|
|
178
|
+
describe(): string | null {
|
|
179
|
+
return describeConnection(this.toWire());
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* What moves the catalog revision when the agent's FILE changes — its path
|
|
184
|
+
* and a digest of its bytes, or the reason it cannot be used. Null for
|
|
185
|
+
* Everything, whose revision is exactly what it was before agents existed.
|
|
186
|
+
*/
|
|
187
|
+
fingerprint(): string | null {
|
|
188
|
+
const s = this.state;
|
|
189
|
+
return s.kind === 'everything' ? null : s.fingerprint;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** See {@link ConnectionScopeWire}; undefined for Everything. */
|
|
193
|
+
toWire(): ConnectionScopeWire | undefined {
|
|
194
|
+
const s = this.state;
|
|
195
|
+
if (s.kind === 'everything') return undefined;
|
|
196
|
+
if (s.kind === 'unavailable') return { agent: s.agent, unavailable: s.sentence };
|
|
197
|
+
return { agent: s.agent };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export {
|
|
2
|
+
noAgentConfigurations,
|
|
3
|
+
resolveConnectionScope,
|
|
4
|
+
type AgentChoice,
|
|
5
|
+
type IAgentScopeResolver,
|
|
6
|
+
} from './agent-scope.contract.js';
|
|
7
|
+
export {
|
|
8
|
+
ConnectionScope,
|
|
9
|
+
NOT_AVAILABLE_TO_CONNECTION,
|
|
10
|
+
connectionRefusal,
|
|
11
|
+
describeConnection,
|
|
12
|
+
type ConnectionScopeWire,
|
|
13
|
+
type ScopeAgent,
|
|
14
|
+
type ScopeRoots,
|
|
15
|
+
} from './connection-scope.js';
|
|
@@ -151,6 +151,7 @@ export class AgentAuditService implements IAgentAuditService, IAgentEventRecorde
|
|
|
151
151
|
deletedAt: k.deletedAt,
|
|
152
152
|
eventCount: counts.get(k.id) ?? 0,
|
|
153
153
|
user: k.user,
|
|
154
|
+
agentPath: k.agentPath ?? null,
|
|
154
155
|
})),
|
|
155
156
|
...connectionRows.map(({ connection: c, user }) => ({
|
|
156
157
|
kind: 'agent' as const,
|
|
@@ -162,6 +163,7 @@ export class AgentAuditService implements IAgentAuditService, IAgentEventRecorde
|
|
|
162
163
|
revokedBy: (c.revokedBy as 'owner' | 'admin' | null) ?? null,
|
|
163
164
|
eventCount: counts.get(c.id) ?? 0,
|
|
164
165
|
user,
|
|
166
|
+
agentPath: c.agentPath ?? null,
|
|
165
167
|
})),
|
|
166
168
|
...pageRows.flatMap((r) =>
|
|
167
169
|
r.pagePath
|
|
@@ -128,6 +128,12 @@ export interface AuditPrincipal {
|
|
|
128
128
|
/** Events currently on record for it (within the retention window). */
|
|
129
129
|
eventCount: number;
|
|
130
130
|
user: { id: string; email: string; name: string };
|
|
131
|
+
/**
|
|
132
|
+
* For a key or an agent connection: the agent configuration it connects as (a
|
|
133
|
+
* repo-relative knowledge-base path), or null for Everything. Absent for a
|
|
134
|
+
* page, which acts as its viewer.
|
|
135
|
+
*/
|
|
136
|
+
agentPath?: string | null;
|
|
131
137
|
}
|
|
132
138
|
|
|
133
139
|
/** One event as the page reads it. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest';
|
|
2
2
|
import type { Request, Response, NextFunction } from 'express';
|
|
3
|
-
import { createAuthMiddleware, AUTH_COOKIE_NAME } from '../auth.middleware.js';
|
|
4
|
-
import { AccountDeactivatedError, AuthBackendError } from '../account-admission.js';
|
|
3
|
+
import { authCookieSecure, createAuthMiddleware, AUTH_COOKIE_NAME } from '../auth.middleware.js';
|
|
4
|
+
import { AccountDeactivatedError, AuthBackendError, SessionEndedError } from '../account-admission.js';
|
|
5
5
|
import type { AuthService } from '../auth.service.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -14,11 +14,13 @@ import type { AuthService } from '../auth.service.js';
|
|
|
14
14
|
const GOOD_TOKEN = 'good-token';
|
|
15
15
|
const OFF_TOKEN = 'switched-off-token';
|
|
16
16
|
const DOWN_TOKEN = 'database-down-token';
|
|
17
|
+
const ENDED_TOKEN = 'password-changed-since-token';
|
|
17
18
|
const IDENTITY = { userId: 'user-1', email: 'alice@example.com' };
|
|
18
19
|
|
|
19
20
|
async function run(headers: Record<string, string>) {
|
|
20
21
|
const resolveSession = vi.fn(async (token: string) => {
|
|
21
22
|
if (token === OFF_TOKEN) throw new AccountDeactivatedError();
|
|
23
|
+
if (token === ENDED_TOKEN) throw new SessionEndedError();
|
|
22
24
|
if (token === DOWN_TOKEN) throw new AuthBackendError(new Error('db down'));
|
|
23
25
|
if (token !== GOOD_TOKEN) throw new Error('bad token');
|
|
24
26
|
return IDENTITY;
|
|
@@ -91,6 +93,32 @@ describe('createAuthMiddleware — a switched-off account', () => {
|
|
|
91
93
|
});
|
|
92
94
|
});
|
|
93
95
|
|
|
96
|
+
describe('authCookieSecure', () => {
|
|
97
|
+
it('marks the cookie Secure for an https deployment, whatever the case of the scheme', () => {
|
|
98
|
+
expect(authCookieSecure('https://kb.example.com')).toBe(true);
|
|
99
|
+
expect(authCookieSecure('HTTPS://kb.example.com/')).toBe(true);
|
|
100
|
+
expect(authCookieSecure('Https://kb.example.com:8443/base')).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('leaves it off for a deployment reached over plain http', () => {
|
|
104
|
+
expect(authCookieSecure('http://localhost:3001')).toBe(false);
|
|
105
|
+
expect(authCookieSecure('HTTP://10.0.0.5')).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('refuses a public URL that is not one, as the rest of the boot does', () => {
|
|
109
|
+
expect(() => authCookieSecure('kb.example.com')).toThrow();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('createAuthMiddleware — a session a password change ended', () => {
|
|
114
|
+
it('refuses it with a 401 that says the password was changed, so the sign-in form can', async () => {
|
|
115
|
+
const { next, status, json } = await run({ cookie: `${AUTH_COOKIE_NAME}=${ENDED_TOKEN}` });
|
|
116
|
+
expect(next).not.toHaveBeenCalled();
|
|
117
|
+
expect(status).toHaveBeenCalledWith(401);
|
|
118
|
+
expect(json.mock.calls[0][0].error).toMatch(/password was changed after this session was opened/);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
94
122
|
describe('createAuthMiddleware — the account cannot be looked up', () => {
|
|
95
123
|
it('500s rather than signing a valid session out during an outage', async () => {
|
|
96
124
|
const { next, status, json } = await run({ authorization: `Bearer ${DOWN_TOKEN}` });
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import express from 'express';
|
|
3
3
|
import type { Server } from 'node:http';
|
|
4
|
-
import { createAuthRoutes } from '../auth.routes.js';
|
|
5
|
-
import type
|
|
4
|
+
import { createAuthRoutes, type AuthRoutesOptions } from '../auth.routes.js';
|
|
5
|
+
import { PasswordChangedMeanwhileError, PasswordRefusedError, type AuthService } from '../auth.service.js';
|
|
6
|
+
import { AUTH_COOKIE_NAME } from '../auth.middleware.js';
|
|
6
7
|
|
|
7
8
|
const authService = {
|
|
8
9
|
loginWithPassword: vi.fn(async () => {
|
|
9
10
|
throw new Error('Invalid credentials');
|
|
10
11
|
}),
|
|
11
12
|
changePassword: vi.fn(async () => {
|
|
12
|
-
throw new
|
|
13
|
+
throw new PasswordRefusedError('Current password is incorrect');
|
|
13
14
|
}),
|
|
14
15
|
getUserById: vi.fn(async () => null),
|
|
15
16
|
} as unknown as AuthService;
|
|
@@ -28,10 +29,10 @@ afterEach(() => {
|
|
|
28
29
|
server?.close();
|
|
29
30
|
});
|
|
30
31
|
|
|
31
|
-
async function listen(): Promise<string> {
|
|
32
|
+
async function listen(options?: AuthRoutesOptions): Promise<string> {
|
|
32
33
|
const app = express();
|
|
33
34
|
app.use(express.json());
|
|
34
|
-
app.use('/api', createAuthRoutes(authService, fakeAuthMiddleware));
|
|
35
|
+
app.use('/api', createAuthRoutes(authService, fakeAuthMiddleware, [], true, options));
|
|
35
36
|
await new Promise<void>((resolve) => {
|
|
36
37
|
server = app.listen(0, resolve);
|
|
37
38
|
});
|
|
@@ -89,3 +90,68 @@ describe('auth routes — rate limiting', () => {
|
|
|
89
90
|
expect((await post()).status).toBe(429);
|
|
90
91
|
});
|
|
91
92
|
});
|
|
93
|
+
|
|
94
|
+
describe('auth routes — change-password', () => {
|
|
95
|
+
const post = (base: string) =>
|
|
96
|
+
fetch(`${base}/api/auth/change-password`, {
|
|
97
|
+
method: 'POST',
|
|
98
|
+
headers: { 'Content-Type': 'application/json' },
|
|
99
|
+
body: JSON.stringify({ currentPassword: 'old-password', newPassword: 'new-password-1' }),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('hands the changer a fresh token, in the body and the auth cookie', async () => {
|
|
103
|
+
vi.mocked(authService.changePassword).mockResolvedValueOnce({ token: 'fresh-jwt' });
|
|
104
|
+
const res = await post(await listen());
|
|
105
|
+
expect(res.status).toBe(200);
|
|
106
|
+
expect(await res.json()).toEqual({ ok: true, token: 'fresh-jwt' });
|
|
107
|
+
expect(res.headers.getSetCookie().some((c) => c.startsWith(`${AUTH_COOKIE_NAME}=fresh-jwt;`))).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('answers a change another session got in first with a 409 and its words, and no cookie', async () => {
|
|
111
|
+
vi.mocked(authService.changePassword).mockRejectedValueOnce(new PasswordChangedMeanwhileError());
|
|
112
|
+
const res = await post(await listen());
|
|
113
|
+
expect(res.status).toBe(409);
|
|
114
|
+
expect(((await res.json()) as { error: string }).error).toMatch(/changed from another session/);
|
|
115
|
+
expect(res.headers.getSetCookie()).toEqual([]);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('answers a refusal the service made on purpose with a 400 and its words', async () => {
|
|
119
|
+
vi.mocked(authService.changePassword).mockRejectedValueOnce(new PasswordRefusedError('Password must be at least 8 characters'));
|
|
120
|
+
const res = await post(await listen());
|
|
121
|
+
expect(res.status).toBe(400);
|
|
122
|
+
expect(await res.json()).toEqual({ error: 'Password must be at least 8 characters' });
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('answers a fault with a generic 500 that carries nothing of the cause', async () => {
|
|
126
|
+
vi.mocked(authService.changePassword).mockRejectedValueOnce(
|
|
127
|
+
new Error('connect ECONNREFUSED db.internal:5432 (role bevel, database bevel)'),
|
|
128
|
+
);
|
|
129
|
+
const res = await post(await listen());
|
|
130
|
+
expect(res.status).toBe(500);
|
|
131
|
+
const body = (await res.json()) as { error: string };
|
|
132
|
+
expect(body).toEqual({ error: 'Could not change the password' });
|
|
133
|
+
expect(JSON.stringify(body)).not.toMatch(/ECONNREFUSED|5432|bevel/);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('marks the cookie Secure when the deployment is reached over https, and not otherwise', async () => {
|
|
137
|
+
vi.mocked(authService.changePassword).mockResolvedValueOnce({ token: 'fresh-jwt' });
|
|
138
|
+
const secure = await post(await listen({ cookieSecure: true }));
|
|
139
|
+
expect(secure.headers.getSetCookie()[0]).toMatch(/; Secure/);
|
|
140
|
+
vi.mocked(authService.changePassword).mockResolvedValueOnce({ token: 'fresh-jwt' });
|
|
141
|
+
const plain = await post(await listen({ cookieSecure: false }));
|
|
142
|
+
expect(plain.headers.getSetCookie()[0]).not.toMatch(/; Secure/);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe('auth routes — me', () => {
|
|
147
|
+
it('answers a fault with a generic 500 that carries nothing of the cause', async () => {
|
|
148
|
+
vi.mocked(authService.getUserById).mockRejectedValueOnce(
|
|
149
|
+
new Error('connect ECONNREFUSED db.internal:5432 (role bevel, database bevel)'),
|
|
150
|
+
);
|
|
151
|
+
const res = await fetch(`${await listen()}/api/auth/me`);
|
|
152
|
+
expect(res.status).toBe(500);
|
|
153
|
+
const body = (await res.json()) as { error: string };
|
|
154
|
+
expect(body).toEqual({ error: 'Could not load your account' });
|
|
155
|
+
expect(JSON.stringify(body)).not.toMatch(/ECONNREFUSED|5432|bevel/);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
@@ -3,11 +3,12 @@ import { PgDialect } from 'drizzle-orm/pg-core';
|
|
|
3
3
|
import type { SQL } from 'drizzle-orm';
|
|
4
4
|
import type { Database } from '../../database/connection.js';
|
|
5
5
|
import type { CoreConfig } from '../../../core-config.js';
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
6
|
+
import jwt from 'jsonwebtoken';
|
|
7
|
+
import { AuthService, PasswordChangedMeanwhileError, PasswordRefusedError } from '../auth.service.js';
|
|
8
|
+
import { AccountAdmissionRefusedError, SessionEndedError, type IAccountAdmission } from '../account-admission.js';
|
|
8
9
|
import { hashPassword, verifyPassword } from '../password-hash.js';
|
|
9
|
-
import { RECOVERY_BOT_EMAIL } from '../../workflow/
|
|
10
|
-
import { DIRECTORY_SYNC_BOT_EMAIL } from '../../access/
|
|
10
|
+
import { RECOVERY_BOT_EMAIL } from '../../workflow/index.js';
|
|
11
|
+
import { DIRECTORY_SYNC_BOT_EMAIL } from '../../access/index.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Minimal drizzle chain stub (same idiom as the secrets-vault tests): each
|
|
@@ -78,8 +79,12 @@ const ROW = {
|
|
|
78
79
|
// always has a boolean here, so the fixture carries one rather than letting
|
|
79
80
|
// every asserted payload read `undefined`.
|
|
80
81
|
onboardingDone: false,
|
|
82
|
+
sessionVersion: 0,
|
|
81
83
|
};
|
|
82
84
|
|
|
85
|
+
/** What a password change's conditional write hands back: the row, one session generation on. */
|
|
86
|
+
const CHANGED = [{ ...ROW, sessionVersion: 1 }];
|
|
87
|
+
|
|
83
88
|
describe('AuthService.loginWithPassword — env bootstrap admin', () => {
|
|
84
89
|
const config = makeConfig({ adminEmail: 'root@example.com', adminPassword: 'sup3r-secret' });
|
|
85
90
|
|
|
@@ -283,14 +288,14 @@ describe('AuthService.createAccount / changePassword', () => {
|
|
|
283
288
|
new AuthService(wrong.db, makeConfig()).changePassword('user-1', 'not-it', 'new-password-1'),
|
|
284
289
|
).rejects.toThrow('Current password is incorrect');
|
|
285
290
|
|
|
286
|
-
const right = makeFakeDb([[{ ...ROW, passwordHash }],
|
|
291
|
+
const right = makeFakeDb([[{ ...ROW, passwordHash }], CHANGED]);
|
|
287
292
|
await expect(
|
|
288
293
|
new AuthService(right.db, makeConfig()).changePassword('user-1', 'old-password', 'new-password-1'),
|
|
289
|
-
).resolves.
|
|
294
|
+
).resolves.toMatchObject({ token: expect.any(String) });
|
|
290
295
|
});
|
|
291
296
|
|
|
292
297
|
it('changePassword lets an SSO-only account set its first password without one', async () => {
|
|
293
|
-
const { db, captured } = makeFakeDb([[{ ...ROW, passwordHash: null }],
|
|
298
|
+
const { db, captured } = makeFakeDb([[{ ...ROW, passwordHash: null }], CHANGED]);
|
|
294
299
|
await new AuthService(db, makeConfig()).changePassword('user-1', undefined, 'first-password');
|
|
295
300
|
const set = captured.set[0] as { passwordHash: string };
|
|
296
301
|
expect(set.passwordHash.startsWith('scrypt:')).toBe(true);
|
|
@@ -452,7 +457,7 @@ describe('AuthService.changePassword — the three account kinds', () => {
|
|
|
452
457
|
});
|
|
453
458
|
|
|
454
459
|
it('stores a new hash for the right current password', async () => {
|
|
455
|
-
const { db, captured } = makeFakeDb([[await row()],
|
|
460
|
+
const { db, captured } = makeFakeDb([[await row()], CHANGED]);
|
|
456
461
|
await new AuthService(db, makeConfig(ENV_ADMIN)).changePassword(
|
|
457
462
|
kind.id,
|
|
458
463
|
'old-password',
|
|
@@ -585,7 +590,7 @@ describe('AuthService.changePassword — the three account kinds', () => {
|
|
|
585
590
|
).getUserById('user-root');
|
|
586
591
|
expect(flagged?.isEnvAdmin).toBe(false);
|
|
587
592
|
|
|
588
|
-
const { db, captured } = makeFakeDb([[{ ...rootRow, passwordHash }],
|
|
593
|
+
const { db, captured } = makeFakeDb([[{ ...rootRow, passwordHash }], CHANGED]);
|
|
589
594
|
await new AuthService(db, config).changePassword('user-root', 'old-password', 'new-password-1');
|
|
590
595
|
expect((captured.set[0] as { passwordHash: string }).passwordHash.startsWith('scrypt:')).toBe(
|
|
591
596
|
true,
|
|
@@ -594,6 +599,82 @@ describe('AuthService.changePassword — the three account kinds', () => {
|
|
|
594
599
|
});
|
|
595
600
|
});
|
|
596
601
|
|
|
602
|
+
/**
|
|
603
|
+
* A password change ends the sessions opened before it. The real-Postgres
|
|
604
|
+
* half (`password-change-sessions.pg.test.ts`) proves it end to end; these
|
|
605
|
+
* pin the service's side without a database.
|
|
606
|
+
*/
|
|
607
|
+
describe('AuthService.changePassword — the sessions opened before it', () => {
|
|
608
|
+
const ON = { deactivatedAt: null };
|
|
609
|
+
|
|
610
|
+
it('refuses a session issued before the change, and accepts the token the change hands back', async () => {
|
|
611
|
+
const passwordHash = await hashPassword('old-password');
|
|
612
|
+
const { db } = makeFakeDb([
|
|
613
|
+
[{ ...ROW, passwordHash }], // sign-in
|
|
614
|
+
[{ ...ROW, passwordHash }], // the change reads the row...
|
|
615
|
+
CHANGED, // ...and writes it one generation on
|
|
616
|
+
[{ ...ON, ...CHANGED[0] }], // the old token's lookup...
|
|
617
|
+
[{ ...ON, ...CHANGED[0] }], // ...and the new one's: a session is never answered from the cache
|
|
618
|
+
]);
|
|
619
|
+
const svc = new AuthService(db, makeConfig());
|
|
620
|
+
const before = await svc.loginWithPassword('alice@example.com', 'old-password');
|
|
621
|
+
const { token } = await svc.changePassword('user-1', 'old-password', 'new-password-1');
|
|
622
|
+
// Named, and worded for the person: the middleware shows these words
|
|
623
|
+
// where a bare "invalid token" would leave them guessing.
|
|
624
|
+
const ended = svc.resolveSession(before.token);
|
|
625
|
+
await expect(ended).rejects.toBeInstanceOf(SessionEndedError);
|
|
626
|
+
await expect(ended).rejects.toThrow(/password was changed after this session was opened/);
|
|
627
|
+
await expect(svc.resolveSession(token)).resolves.toEqual({ userId: ROW.id, email: ROW.email });
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
it('refuses a wrong current password as a refusal the caller is told, not a fault', async () => {
|
|
631
|
+
const passwordHash = await hashPassword('old-password');
|
|
632
|
+
const { db } = makeFakeDb([[{ ...ROW, passwordHash }]]);
|
|
633
|
+
const refused = new AuthService(db, makeConfig()).changePassword('user-1', 'not-it', 'new-password-1');
|
|
634
|
+
await expect(refused).rejects.toBeInstanceOf(PasswordRefusedError);
|
|
635
|
+
await expect(refused).rejects.toThrow('Current password is incorrect');
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
it('reads the account row for every session it resolves, whatever the cache holds', async () => {
|
|
639
|
+
// Another replica's password change is only visible in the row, so a
|
|
640
|
+
// session answered from the cache would outlive it by up to 30 seconds.
|
|
641
|
+
const live = [{ ...ON, ...ROW }];
|
|
642
|
+
const { db } = makeFakeDb([live, live, live]);
|
|
643
|
+
const svc = new AuthService(db, makeConfig());
|
|
644
|
+
const token = jwt.sign({ userId: ROW.id, email: ROW.email, sv: 0 }, 'test-jwt-secret', { expiresIn: '7d' });
|
|
645
|
+
const select = vi.mocked(db.select);
|
|
646
|
+
|
|
647
|
+
await svc.resolveSession(token);
|
|
648
|
+
expect(select).toHaveBeenCalledTimes(1);
|
|
649
|
+
// The cache now holds the account; isActive may use it...
|
|
650
|
+
await expect(svc.isActive(ROW.id)).resolves.toBe(true);
|
|
651
|
+
expect(select).toHaveBeenCalledTimes(1);
|
|
652
|
+
// ...but a session never does.
|
|
653
|
+
await svc.resolveSession(token);
|
|
654
|
+
expect(select).toHaveBeenCalledTimes(2);
|
|
655
|
+
await svc.resolveSession(token);
|
|
656
|
+
expect(select).toHaveBeenCalledTimes(3);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
it('keeps a token issued before sessions had generations while the password never changed', async () => {
|
|
660
|
+
const { db } = makeFakeDb([[{ ...ON, ...ROW }]]);
|
|
661
|
+
const legacy = jwt.sign({ userId: ROW.id, email: ROW.email }, 'test-jwt-secret', { expiresIn: '7d' });
|
|
662
|
+
await expect(new AuthService(db, makeConfig()).resolveSession(legacy)).resolves.toEqual({
|
|
663
|
+
userId: ROW.id,
|
|
664
|
+
email: ROW.email,
|
|
665
|
+
});
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
it('is refused, in words, when another session changed the password between the read and the write', async () => {
|
|
669
|
+
const passwordHash = await hashPassword('old-password');
|
|
670
|
+
// The conditional write finds the row no longer at the generation read.
|
|
671
|
+
const { db } = makeFakeDb([[{ ...ROW, passwordHash }], []]);
|
|
672
|
+
const refused = new AuthService(db, makeConfig()).changePassword('user-1', 'old-password', 'new-password-1');
|
|
673
|
+
await expect(refused).rejects.toBeInstanceOf(PasswordChangedMeanwhileError);
|
|
674
|
+
await expect(refused).rejects.toThrow(/changed from another session.*nothing was changed/);
|
|
675
|
+
});
|
|
676
|
+
});
|
|
677
|
+
|
|
597
678
|
describe('AuthService.listAccounts', () => {
|
|
598
679
|
it('reports hasPassword without ever exposing the hash', async () => {
|
|
599
680
|
const passwordHash = await hashPassword('pw-longer-than-8');
|