@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
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
AccountChangeRefusedError,
|
|
10
10
|
AccountDeactivatedError,
|
|
11
11
|
AuthBackendError,
|
|
12
|
+
SessionEndedError,
|
|
12
13
|
admitEveryone,
|
|
13
14
|
type AccountProvisionReason,
|
|
14
15
|
type IAccountAdmission,
|
|
@@ -32,6 +33,8 @@ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
|
32
33
|
* request carrying a credential asks, so the answer is kept briefly rather
|
|
33
34
|
* than read each time; switching an account off or on in THIS process
|
|
34
35
|
* forgets it at once, and another replica sees the change within this long.
|
|
36
|
+
* A session token never waits on it: {@link AuthService.resolveSession}
|
|
37
|
+
* reads the row each time, so a password change ends it everywhere at once.
|
|
35
38
|
*/
|
|
36
39
|
const ACTIVE_CACHE_MS = 30_000;
|
|
37
40
|
|
|
@@ -69,6 +72,33 @@ export const OWNER_SWITCH_OFF_REFUSAL = "The owner can't be switched off.";
|
|
|
69
72
|
/** What an admin is told when deleting an owner's account would leave them no way to sign in. */
|
|
70
73
|
export const OWNER_DELETE_REFUSAL = "The owner's account can't be deleted: they would have no way to sign in.";
|
|
71
74
|
|
|
75
|
+
/**
|
|
76
|
+
* A password change refused because the password it proved is no longer the
|
|
77
|
+
* account's: another session changed it between this change's read and its
|
|
78
|
+
* write. Nothing was written; the routes answer it with a 409 and its words.
|
|
79
|
+
*/
|
|
80
|
+
export class PasswordChangedMeanwhileError extends Error {
|
|
81
|
+
constructor() {
|
|
82
|
+
super('Your password was changed from another session while this change was being made, so nothing was changed. Sign in with the new password.');
|
|
83
|
+
this.name = 'PasswordChangedMeanwhileError';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A password write the service refuses on purpose: the policy, a wrong
|
|
89
|
+
* current password, an account whose password is not kept here. The message
|
|
90
|
+
* is written for the caller, and a route answers it with a 400 — unlike any
|
|
91
|
+
* other error a password write raises, which is a fault and answered generic.
|
|
92
|
+
*/
|
|
93
|
+
export class PasswordRefusedError extends Error {
|
|
94
|
+
readonly status = 400;
|
|
95
|
+
|
|
96
|
+
constructor(message: string) {
|
|
97
|
+
super(message);
|
|
98
|
+
this.name = 'PasswordRefusedError';
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
72
102
|
/**
|
|
73
103
|
* Decoy hash verified when the email is unknown or has no password set, so
|
|
74
104
|
* those paths cost the same scrypt work as a real wrong-password attempt —
|
|
@@ -271,7 +301,7 @@ export class AuthService implements IAccountSwitchGuard {
|
|
|
271
301
|
const defaultName = normalizedEmail.split('@')[0] || normalizedEmail;
|
|
272
302
|
await this.assertAdmitted(normalizedEmail, 'bootstrap');
|
|
273
303
|
const admin = await this.clearOwnerSwitchOff(await this.upsertUserByEmail(normalizedEmail, defaultName));
|
|
274
|
-
return { token: this.signToken(admin
|
|
304
|
+
return { token: this.signToken(admin), user: this.toClientUser(admin) };
|
|
275
305
|
}
|
|
276
306
|
|
|
277
307
|
// Always run one scrypt verification — against the stored hash when there
|
|
@@ -287,7 +317,7 @@ export class AuthService implements IAccountSwitchGuard {
|
|
|
287
317
|
// is switched off would tell them it has an account.
|
|
288
318
|
const signedIn = await this.clearOwnerSwitchOff(user);
|
|
289
319
|
if (signedIn.deactivatedAt) throw new AccountDeactivatedError();
|
|
290
|
-
return { token: this.signToken(signedIn
|
|
320
|
+
return { token: this.signToken(signedIn), user: this.toClientUser(signedIn) };
|
|
291
321
|
}
|
|
292
322
|
|
|
293
323
|
/**
|
|
@@ -319,7 +349,7 @@ export class AuthService implements IAccountSwitchGuard {
|
|
|
319
349
|
const displayName = (name ?? '').trim() || normalizedEmail.split('@')[0] || normalizedEmail;
|
|
320
350
|
const user = await this.clearOwnerSwitchOff(await this.upsertUserByEmail(normalizedEmail, displayName));
|
|
321
351
|
if (user.deactivatedAt) throw new AccountDeactivatedError();
|
|
322
|
-
return { token: this.signToken(user
|
|
352
|
+
return { token: this.signToken(user), user: this.toClientUser(user) };
|
|
323
353
|
}
|
|
324
354
|
|
|
325
355
|
/**
|
|
@@ -361,7 +391,7 @@ export class AuthService implements IAccountSwitchGuard {
|
|
|
361
391
|
// than sending the admin off to pick a longer password first.
|
|
362
392
|
this.assertNotSystemAccount(normalizedEmail);
|
|
363
393
|
if (this.isEnvAdminEmail(normalizedEmail)) {
|
|
364
|
-
throw new
|
|
394
|
+
throw new PasswordRefusedError(ENV_ADMIN_PASSWORD_REFUSAL);
|
|
365
395
|
}
|
|
366
396
|
this.assertPasswordPolicy(password);
|
|
367
397
|
await this.assertAdmitted(normalizedEmail, 'admin-create');
|
|
@@ -370,16 +400,22 @@ export class AuthService implements IAccountSwitchGuard {
|
|
|
370
400
|
// EXPLICITLY supplied name is persisted too; a blank name keeps the
|
|
371
401
|
// existing display name rather than clobbering it with the local-part
|
|
372
402
|
// fallback. `returning()` yields the authoritative row either way.
|
|
403
|
+
// A reset is a password change like any other: the session generation
|
|
404
|
+
// moves on with it, so every session opened with the old password ends,
|
|
405
|
+
// and a self-service change that read the row before it is refused
|
|
406
|
+
// rather than overwriting the reset (see {@link changePassword}).
|
|
407
|
+
const sessionVersion = sql`${users.sessionVersion} + 1`;
|
|
373
408
|
const [row] = await this.db
|
|
374
409
|
.insert(users)
|
|
375
410
|
.values({ email: normalizedEmail, emailBidx: normalizedEmail, name: displayName, passwordHash })
|
|
376
411
|
.onConflictDoUpdate({
|
|
377
412
|
target: users.emailBidx,
|
|
378
413
|
set: suppliedName
|
|
379
|
-
? { passwordHash, name: suppliedName, updatedAt: new Date() }
|
|
380
|
-
: { passwordHash, updatedAt: new Date() },
|
|
414
|
+
? { passwordHash, sessionVersion, name: suppliedName, updatedAt: new Date() }
|
|
415
|
+
: { passwordHash, sessionVersion, updatedAt: new Date() },
|
|
381
416
|
})
|
|
382
417
|
.returning();
|
|
418
|
+
this.activeCache.delete(row.id);
|
|
383
419
|
return this.toClientUser(row);
|
|
384
420
|
}
|
|
385
421
|
|
|
@@ -443,21 +479,26 @@ export class AuthService implements IAccountSwitchGuard {
|
|
|
443
479
|
if (existing) {
|
|
444
480
|
return { user: this.toClientUser(existing), created: false, passwordSet: false, deactivated: existing.deactivatedAt != null };
|
|
445
481
|
}
|
|
446
|
-
throw new
|
|
482
|
+
throw new PasswordRefusedError(ENV_ADMIN_PASSWORD_REFUSAL);
|
|
447
483
|
}
|
|
448
484
|
this.assertPasswordPolicy(password);
|
|
449
485
|
await this.assertAdmitted(normalizedEmail, 'admin-create');
|
|
450
486
|
const passwordHash = await hashPassword(password);
|
|
451
487
|
const displayName = normalizedEmail.split('@')[0] || normalizedEmail;
|
|
488
|
+
// A first password on an existing account is a password write like any
|
|
489
|
+
// other, so the session generation moves on with it: a self-service
|
|
490
|
+
// first password that read the row before this one is refused rather
|
|
491
|
+
// than overwriting it (see {@link changePassword}).
|
|
452
492
|
const [row] = await this.db
|
|
453
493
|
.insert(users)
|
|
454
494
|
.values({ email: normalizedEmail, emailBidx: normalizedEmail, name: displayName, passwordHash })
|
|
455
495
|
.onConflictDoUpdate({
|
|
456
496
|
target: users.emailBidx,
|
|
457
|
-
set: { passwordHash, updatedAt: new Date() },
|
|
497
|
+
set: { passwordHash, sessionVersion: sql`${users.sessionVersion} + 1`, updatedAt: new Date() },
|
|
458
498
|
setWhere: and(isNull(users.passwordHash), isNull(users.deactivatedAt)),
|
|
459
499
|
})
|
|
460
500
|
.returning({ ...getTableColumns(users), created: CREATED });
|
|
501
|
+
if (row) this.activeCache.delete(row.id);
|
|
461
502
|
if (row) return { user: this.toClientUser(row), created: row.created === true, passwordSet: true, deactivated: false };
|
|
462
503
|
// The conflicting account has a password or is switched off: untouched.
|
|
463
504
|
const [existing] = await this.db.select().from(users).where(eq(users.emailBidx, normalizedEmail)).limit(1);
|
|
@@ -586,31 +627,49 @@ export class AuthService implements IAccountSwitchGuard {
|
|
|
586
627
|
* environment password is the platform's rescue path into a deployment and
|
|
587
628
|
* is changed there, which is what the Account page says in place of the
|
|
588
629
|
* form.
|
|
630
|
+
*
|
|
631
|
+
* A change ends every session opened before it: the row's
|
|
632
|
+
* `sessionVersion` moves on in the same write, so every token issued
|
|
633
|
+
* before it is refused at its next request (see {@link resolveSession}).
|
|
634
|
+
* The caller's own session is the one exception: the token returned here
|
|
635
|
+
* carries the new generation and is what the caller keeps using.
|
|
636
|
+
*
|
|
637
|
+
* The write only lands on the row as it was read. If another session
|
|
638
|
+
* changed the password between the read and the write, the current
|
|
639
|
+
* password this one proved is no longer current, so it is refused with
|
|
640
|
+
* {@link PasswordChangedMeanwhileError} and nothing is written. Two clicks
|
|
641
|
+
* of one form are the same race.
|
|
589
642
|
*/
|
|
590
643
|
async changePassword(
|
|
591
644
|
userId: string,
|
|
592
645
|
currentPassword: string | undefined,
|
|
593
646
|
newPassword: string,
|
|
594
|
-
): Promise<
|
|
647
|
+
): Promise<{ token: string }> {
|
|
595
648
|
const [user] = await this.db.select().from(users).where(eq(users.id, userId)).limit(1);
|
|
596
|
-
if (!user) throw new
|
|
649
|
+
if (!user) throw new PasswordRefusedError('User not found');
|
|
597
650
|
// Before the policy check, so the deployment admin is told the real reason
|
|
598
651
|
// rather than being sent to fix a password that would be refused anyway.
|
|
599
652
|
this.assertNotSystemAccount(user.email);
|
|
600
653
|
if (this.isEnvAdminEmail(user.email)) {
|
|
601
|
-
throw new
|
|
654
|
+
throw new PasswordRefusedError(ENV_ADMIN_PASSWORD_REFUSAL);
|
|
602
655
|
}
|
|
603
656
|
this.assertPasswordPolicy(newPassword);
|
|
604
657
|
if (user.passwordHash) {
|
|
605
658
|
if (!currentPassword || !(await verifyPassword(currentPassword, user.passwordHash))) {
|
|
606
|
-
throw new
|
|
659
|
+
throw new PasswordRefusedError('Current password is incorrect');
|
|
607
660
|
}
|
|
608
661
|
}
|
|
609
662
|
const passwordHash = await hashPassword(newPassword);
|
|
610
|
-
await this.db
|
|
663
|
+
const [changed] = await this.db
|
|
611
664
|
.update(users)
|
|
612
|
-
.set({ passwordHash, updatedAt: new Date() })
|
|
613
|
-
.where(eq(users.id, userId))
|
|
665
|
+
.set({ passwordHash, sessionVersion: sql`${users.sessionVersion} + 1`, updatedAt: new Date() })
|
|
666
|
+
.where(and(eq(users.id, userId), eq(users.sessionVersion, user.sessionVersion)))
|
|
667
|
+
.returning();
|
|
668
|
+
if (!changed) throw new PasswordChangedMeanwhileError();
|
|
669
|
+
// Sessions read the row fresh (see resolveSession); the cached entry is
|
|
670
|
+
// dropped so nothing in this process keeps the old generation either.
|
|
671
|
+
this.activeCache.delete(userId);
|
|
672
|
+
return { token: this.signToken(changed) };
|
|
614
673
|
}
|
|
615
674
|
|
|
616
675
|
/**
|
|
@@ -664,18 +723,24 @@ export class AuthService implements IAccountSwitchGuard {
|
|
|
664
723
|
/** Every password write calls this first: the system accounts never get one. */
|
|
665
724
|
private assertNotSystemAccount(normalizedEmail: string): void {
|
|
666
725
|
if (SYSTEM_ACCOUNT_EMAILS.includes(normalizedEmail)) {
|
|
667
|
-
throw new
|
|
726
|
+
throw new PasswordRefusedError(SYSTEM_ACCOUNT_PASSWORD_REFUSAL);
|
|
668
727
|
}
|
|
669
728
|
}
|
|
670
729
|
|
|
671
730
|
private assertPasswordPolicy(password: string): void {
|
|
672
731
|
if ((password ?? '').length < MIN_PASSWORD_LENGTH) {
|
|
673
|
-
throw new
|
|
732
|
+
throw new PasswordRefusedError(`Password must be at least ${MIN_PASSWORD_LENGTH} characters`);
|
|
674
733
|
}
|
|
675
734
|
}
|
|
676
735
|
|
|
677
|
-
|
|
678
|
-
|
|
736
|
+
/**
|
|
737
|
+
* A session token for `user`, stamped with the session generation its row
|
|
738
|
+
* is at (`sv`): {@link resolveSession} refuses it once the row moves on.
|
|
739
|
+
*/
|
|
740
|
+
private signToken(user: { id: string; email: string; sessionVersion: number }): string {
|
|
741
|
+
return jwt.sign({ userId: user.id, email: user.email, sv: user.sessionVersion }, this.config.jwtSecret, {
|
|
742
|
+
expiresIn: '7d',
|
|
743
|
+
});
|
|
679
744
|
}
|
|
680
745
|
|
|
681
746
|
/**
|
|
@@ -710,39 +775,55 @@ export class AuthService implements IAccountSwitchGuard {
|
|
|
710
775
|
|
|
711
776
|
/**
|
|
712
777
|
* The signature and expiry of a session token, and nothing else — whether
|
|
713
|
-
* its account is still on is
|
|
778
|
+
* its account is still on, and whether the session is still honoured, are
|
|
779
|
+
* {@link resolveSession}'s questions. Every
|
|
714
780
|
* place a session is ACCEPTED goes through that one.
|
|
715
781
|
*/
|
|
716
782
|
verifyToken(token: string): { userId: string; email: string } {
|
|
783
|
+
const { userId, email } = this.decodeSession(token);
|
|
784
|
+
return { userId, email };
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/** {@link verifyToken} plus the session generation the token was issued at. */
|
|
788
|
+
private decodeSession(token: string): { userId: string; email: string; sessionVersion: number } {
|
|
717
789
|
const decoded = jwt.verify(token, this.config.jwtSecret) as {
|
|
718
790
|
userId: string;
|
|
719
791
|
email: string;
|
|
792
|
+
sv?: number;
|
|
720
793
|
};
|
|
721
|
-
|
|
794
|
+
// A token issued before sessions had generations carries none: it is of
|
|
795
|
+
// the first one, which is where every row started.
|
|
796
|
+
return { userId: decoded.userId, email: decoded.email, sessionVersion: decoded.sv ?? 0 };
|
|
722
797
|
}
|
|
723
798
|
|
|
724
799
|
/**
|
|
725
|
-
* A session token that may still be used: signed by us, not expired,
|
|
726
|
-
*
|
|
800
|
+
* A session token that may still be used: signed by us, not expired, its
|
|
801
|
+
* account still on, and issued since the account's password last changed.
|
|
802
|
+
* A token outlives a deactivation or a password change by up to seven
|
|
727
803
|
* days, so the signature alone no longer says the bearer may enter.
|
|
728
|
-
* Throws for an invalid token, {@link
|
|
729
|
-
*
|
|
804
|
+
* Throws for an invalid token, {@link SessionEndedError} for one a
|
|
805
|
+
* password change ended, {@link AccountDeactivatedError} for one whose
|
|
806
|
+
* account was switched off.
|
|
730
807
|
*/
|
|
731
808
|
async resolveSession(token: string): Promise<{ userId: string; email: string }> {
|
|
732
|
-
const claim = this.
|
|
733
|
-
let active: boolean;
|
|
809
|
+
const { sessionVersion, ...claim } = this.decodeSession(token);
|
|
810
|
+
let account: { active: boolean; sessionVersion: number };
|
|
734
811
|
try {
|
|
735
|
-
|
|
812
|
+
// Read fresh, never from the cache: a password changed on another
|
|
813
|
+
// replica must end this token at its very next request, and only the
|
|
814
|
+
// row can say it was.
|
|
815
|
+
account = await this.accountState(claim.userId, { fresh: true });
|
|
736
816
|
} catch (err) {
|
|
737
817
|
// The token is fine; the database is not. Said apart from a refusal, so
|
|
738
818
|
// an outage is a 500 and not a sign-out (see `AuthBackendError`).
|
|
739
819
|
throw new AuthBackendError(err);
|
|
740
820
|
}
|
|
741
|
-
if (!active) throw new AccountDeactivatedError();
|
|
821
|
+
if (!account.active) throw new AccountDeactivatedError();
|
|
822
|
+
if (sessionVersion !== account.sessionVersion) throw new SessionEndedError();
|
|
742
823
|
return claim;
|
|
743
824
|
}
|
|
744
825
|
|
|
745
|
-
private readonly activeCache = new Map<string, { active: boolean; at: number }>();
|
|
826
|
+
private readonly activeCache = new Map<string, { active: boolean; sessionVersion: number; at: number }>();
|
|
746
827
|
|
|
747
828
|
/**
|
|
748
829
|
* Whether the account behind a credential is on: it exists and no admin
|
|
@@ -762,18 +843,38 @@ export class AuthService implements IAccountSwitchGuard {
|
|
|
762
843
|
* ({@link clearOwnerSwitchOff}).
|
|
763
844
|
*/
|
|
764
845
|
async isActive(userId: string): Promise<boolean> {
|
|
846
|
+
return (await this.accountState(userId)).active;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* {@link isActive}'s answer and the session generation the account is at,
|
|
851
|
+
* read and cached together; a password change in this process forgets the
|
|
852
|
+
* entry at once, as switching an account off does. `fresh` reads the row
|
|
853
|
+
* whatever the cache holds (and refreshes it): {@link resolveSession}'s
|
|
854
|
+
* question, since a session's generation must hold on every replica.
|
|
855
|
+
*/
|
|
856
|
+
private async accountState(
|
|
857
|
+
userId: string,
|
|
858
|
+
{ fresh = false }: { fresh?: boolean } = {},
|
|
859
|
+
): Promise<{ active: boolean; sessionVersion: number }> {
|
|
765
860
|
const now = Date.now();
|
|
766
861
|
const hit = this.activeCache.get(userId);
|
|
767
|
-
if (hit && now - hit.at < ACTIVE_CACHE_MS) return hit
|
|
862
|
+
if (!fresh && hit && now - hit.at < ACTIVE_CACHE_MS) return hit;
|
|
768
863
|
const [found] = await this.db
|
|
769
|
-
.select({
|
|
864
|
+
.select({
|
|
865
|
+
id: users.id,
|
|
866
|
+
email: users.email,
|
|
867
|
+
deactivatedAt: users.deactivatedAt,
|
|
868
|
+
sessionVersion: users.sessionVersion,
|
|
869
|
+
})
|
|
770
870
|
.from(users)
|
|
771
871
|
.where(eq(users.id, userId))
|
|
772
872
|
.limit(1);
|
|
773
873
|
const row = found && (await this.clearOwnerSwitchOff(found));
|
|
774
874
|
const active = row !== undefined && (!row.deactivatedAt || this.isEnvAdminEmail(row.email ?? ''));
|
|
775
|
-
|
|
776
|
-
|
|
875
|
+
const entry = { active, sessionVersion: row?.sessionVersion ?? 0, at: now };
|
|
876
|
+
this.activeCache.set(userId, entry);
|
|
877
|
+
return entry;
|
|
777
878
|
}
|
|
778
879
|
|
|
779
880
|
/**
|
|
@@ -4,8 +4,7 @@ import { logger } from '../../shared/logging.js';
|
|
|
4
4
|
const log = logger('oidc');
|
|
5
5
|
import type express from 'express';
|
|
6
6
|
import type { AuthProviderPlugin } from './auth.routes.js';
|
|
7
|
-
import {
|
|
8
|
-
import { AUTH_COOKIE_NAME } from './auth.middleware.js';
|
|
7
|
+
import { setAuthCookie } from './auth.middleware.js';
|
|
9
8
|
import type { AuthService } from './auth.service.js';
|
|
10
9
|
import { AccountAdmissionRefusedError, AccountDeactivatedError } from './account-admission.js';
|
|
11
10
|
import { normalizeIssuerUrl } from '../settings/index.js';
|
|
@@ -311,13 +310,7 @@ export class OidcAuthProvider implements AuthProviderPlugin {
|
|
|
311
310
|
} catch (error) {
|
|
312
311
|
log.error('OIDC sign-in record failed:', { err: error });
|
|
313
312
|
}
|
|
314
|
-
res
|
|
315
|
-
httpOnly: true,
|
|
316
|
-
sameSite: 'lax',
|
|
317
|
-
secure: cookieSecure,
|
|
318
|
-
maxAge: AUTH_COOKIE_MAX_AGE_S * 1000,
|
|
319
|
-
path: '/',
|
|
320
|
-
});
|
|
313
|
+
setAuthCookie(res, token, cookieSecure);
|
|
321
314
|
res.redirect(`${publicFrontendUrl}/auth/oidc/callback#token=${encodeURIComponent(token)}`);
|
|
322
315
|
} catch (error) {
|
|
323
316
|
// A refused provisioning is not a failed sign-in: the identity was
|
|
@@ -51,7 +51,7 @@ export function createCallToolChainTool(
|
|
|
51
51
|
`There is NO single calling shape: some tools take their arguments under a \`body\` object, others take them flat. Call each tool as the \`Call:\` line at the top of its own description shows — it is generated from that tool's input schema, and every tool in \`${ns}\` has one. Every argument a tool declares REQUIRED must be present; for the knowledge-base tools that includes \`branch\`. Arguments that do not match the schema are refused before anything is sent or run, and the refusal carries the tool's interface.`,
|
|
52
52
|
'Error handling inside the chain: a failing tool call THROWS, and the thrown error\'s `.message` holds the server\'s actual reason (e.g. a 403 with the explanation, not just a status code). If you catch it, surface `err.message` (and `err.status` / `err.data` when present) — NEVER `return { error: err }` or otherwise return the raw Error object, because an Error serializes to `{}` (its `message` is non-enumerable) and the reason is lost. If you don\'t need to handle it, just let it throw — the runtime already reports `err.message` back to you.',
|
|
53
53
|
`Failures are answered, never dropped: a chain that throws comes back with \`success: false\` and the reason, and one that outlives \`timeout\` (default ${CHAIN_TIMEOUT_DEFAULT_MS} ms, maximum ${CHAIN_TIMEOUT_MAX_MS} ms) comes back saying it timed out — raise \`timeout\` or split the work and run it again. Either way your next tool call works as usual.`,
|
|
54
|
-
'Large return values: if the returned value exceeds `max_output_size`, the full JSON is auto-spilled to a shared spill store (outside any workspace, never committed) and the response contains only a `__tool_chain_spill__/…` ref + a truncated marker. You can read the spill back with the regular `read_file` tool — pass that ref as `path` (
|
|
54
|
+
'Large return values: if the returned value exceeds `max_output_size`, the full JSON is auto-spilled to a shared spill store (outside any workspace, never committed) and the response contains only a `__tool_chain_spill__/…` ref + a truncated marker. You can read the spill back with the regular `read_file` tool — pass that ref as `path` (any existing branch as `branch`; it is not used) plus `offset` / `limit` to slice it, never read a multi-MB file in full. Order of preference: (1) re-run `call_tool_chain` with a follow-up code chain that filters/maps the data inline and returns just what you need; (2) narrow the API call — shorter `fields`, tighter date window, lower `limit`; (3) last resort — `read_file` against the spill ref with `offset` / `limit`. The spill is read-only context only; do NOT use it as a way to persist KB content — for KB writes use the regular `write_file` / `edit_file` tools, which go through the lock/commit pipeline.',
|
|
55
55
|
].join('\n\n'),
|
|
56
56
|
inputSchema: z.object({
|
|
57
57
|
code: z
|
|
@@ -121,7 +121,7 @@ export function createCallToolChainTool(
|
|
|
121
121
|
truncated: true,
|
|
122
122
|
result_ref: ref,
|
|
123
123
|
result_bytes: bytes,
|
|
124
|
-
message: `Combined result+logs payload was ${fullJson.length} characters (exceeded max_output_size of ${maxOutputSize}). Full JSON (both \`result\` and \`logs\`) saved to the shared spill store as \`${ref}\` (outside any workspace, uncommitted). Read it back with \`read_file\` — pass that ref as \`path\` (\`branch
|
|
124
|
+
message: `Combined result+logs payload was ${fullJson.length} characters (exceeded max_output_size of ${maxOutputSize}). Full JSON (both \`result\` and \`logs\`) saved to the shared spill store as \`${ref}\` (outside any workspace, uncommitted). Read it back with \`read_file\` — pass that ref as \`path\` (any existing branch as \`branch\`; it is not used) plus \`offset\` / \`limit\` for a slice — or, usually better, narrow the next \`call_tool_chain\` call (smaller fields list, tighter date window, lower limit) so the result fits inline.`,
|
|
125
125
|
};
|
|
126
126
|
} catch (err) {
|
|
127
127
|
// The chain itself SUCCEEDED and only its delivery failed, so the logs
|
|
@@ -4,8 +4,8 @@ import type {
|
|
|
4
4
|
IToolManualService,
|
|
5
5
|
ToolHealthCheck,
|
|
6
6
|
ToolProbeTarget,
|
|
7
|
-
} from '../../tool-manuals/
|
|
8
|
-
import type { ISecretsVaultService } from '../../secrets-vault/
|
|
7
|
+
} from '../../tool-manuals/index.js';
|
|
8
|
+
import type { ISecretsVaultService } from '../../secrets-vault/index.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* The three-way verdict is the whole point of this module, and the line that
|
|
@@ -33,7 +33,7 @@ vi.mock('@utcp/code-mode', () => ({
|
|
|
33
33
|
CodeModeUtcpClient: { create: () => createClientMock() },
|
|
34
34
|
}));
|
|
35
35
|
|
|
36
|
-
const { ConnectionProbeService
|
|
36
|
+
const { ConnectionProbeService } = await import('../connection-probe.service.js');
|
|
37
37
|
|
|
38
38
|
const DECLARED_PROBE: ToolHealthCheck = {
|
|
39
39
|
url: 'https://api.acme.test/me',
|
|
@@ -739,25 +739,6 @@ describe('ConnectionProbeService: what the probe concludes', () => {
|
|
|
739
739
|
expect(r?.detail!.length).toBeLessThan(400);
|
|
740
740
|
expect(cancelled).toBe(true);
|
|
741
741
|
});
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
* The cap has to bound what is DECODED, not merely how many times we loop.
|
|
745
|
-
* Counting a chunk against the budget after decoding it lets a provider that
|
|
746
|
-
* answers in one huge chunk put the whole thing through the decoder — the
|
|
747
|
-
* bound bypassed by exactly the party it exists to bound. Asserted on
|
|
748
|
-
* `readCapped` directly because the caller's 200-character snippet is
|
|
749
|
-
* identical either way, which is how the first version of this passed.
|
|
750
|
-
*/
|
|
751
|
-
it('holds one oversized chunk to the byte budget, not just the loop', async () => {
|
|
752
|
-
const oneHugeChunk = new ReadableStream<Uint8Array>({
|
|
753
|
-
start(controller) {
|
|
754
|
-
controller.enqueue(new TextEncoder().encode('x'.repeat(5_000_000)));
|
|
755
|
-
controller.close();
|
|
756
|
-
},
|
|
757
|
-
});
|
|
758
|
-
|
|
759
|
-
const out = await readCapped(new Response(oneHugeChunk), 8 * 1024);
|
|
760
|
-
|
|
761
|
-
expect(out.length).toBe(8 * 1024);
|
|
762
|
-
});
|
|
742
|
+
// The byte budget's own property (one oversized chunk held to the cap) is
|
|
743
|
+
// asserted on the shared reader in `shared/__tests__/read-capped.test.ts`.
|
|
763
744
|
});
|
|
@@ -12,6 +12,7 @@ import type { ISecretsVaultService } from '../secrets-vault/index.js';
|
|
|
12
12
|
import { bevelSecretsLoaderConfig } from '../secrets-vault/index.js';
|
|
13
13
|
import { utcpNamespacedKey } from '../../shared/utcp-namespace.js';
|
|
14
14
|
import { assertSafeFetchUrl } from '../../shared/ssrf.js';
|
|
15
|
+
import { readCapped } from '../../shared/read-capped.js';
|
|
15
16
|
import type { IConnectionProbeService, ProbeStatus, ProbeVerdict } from './connection-probe.contract.js';
|
|
16
17
|
|
|
17
18
|
/** How long a probe may take before we call it inconclusive. */
|
|
@@ -753,37 +754,3 @@ async function describeRejection(res: Response, redactor: SecretRedactor): Promi
|
|
|
753
754
|
}
|
|
754
755
|
}
|
|
755
756
|
|
|
756
|
-
/**
|
|
757
|
-
* At most `limit` bytes of a response body, decoded as text.
|
|
758
|
-
*
|
|
759
|
-
* Each CHUNK is sliced to the remaining budget before being decoded, not just
|
|
760
|
-
* counted against it: a provider that answers in one 5MB chunk would otherwise
|
|
761
|
-
* put the whole thing through the decoder before the loop noticed it was over,
|
|
762
|
-
* which is the bound bypassed by the very party it is meant to bound.
|
|
763
|
-
*
|
|
764
|
-
* Exported for its test — the property here is how much is held in memory,
|
|
765
|
-
* which the caller's 200-character snippet hides completely.
|
|
766
|
-
*/
|
|
767
|
-
export async function readCapped(res: Response, limit: number): Promise<string> {
|
|
768
|
-
const reader = res.body?.getReader();
|
|
769
|
-
// No stream (a mocked or already-consumed response): fall back to the whole
|
|
770
|
-
// body, which for those cases is ours and small.
|
|
771
|
-
if (!reader) return (await res.text()).slice(0, limit);
|
|
772
|
-
const decoder = new TextDecoder();
|
|
773
|
-
let out = '';
|
|
774
|
-
let read = 0;
|
|
775
|
-
try {
|
|
776
|
-
while (read < limit) {
|
|
777
|
-
const { done, value } = await reader.read();
|
|
778
|
-
if (done) break;
|
|
779
|
-
const remaining = limit - read;
|
|
780
|
-
const chunk = value.byteLength > remaining ? value.subarray(0, remaining) : value;
|
|
781
|
-
read += chunk.byteLength;
|
|
782
|
-
out += decoder.decode(chunk, { stream: true });
|
|
783
|
-
if (chunk.byteLength < value.byteLength) break;
|
|
784
|
-
}
|
|
785
|
-
} finally {
|
|
786
|
-
await reader.cancel().catch(() => {});
|
|
787
|
-
}
|
|
788
|
-
return out;
|
|
789
|
-
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { afterAll, describe, expect, it } from 'vitest';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import { cpSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { migrate } from 'drizzle-orm/node-postgres/migrator';
|
|
7
|
+
import { sql } from 'drizzle-orm';
|
|
8
|
+
import { closeDb, createDb, type Database } from '../connection.js';
|
|
9
|
+
import { runCoreMigrations, runPiiEncryptionBackfill } from '../migrate.js';
|
|
10
|
+
import { coreMigrationsDir } from '../../../assets.js';
|
|
11
|
+
import { IndexOnWrite, SealOnWrite } from '../../../shared/column-crypto.js';
|
|
12
|
+
import { PG_ADMIN_URL, PG_TEST_TIMEOUT, dropScratchDatabases, scratchDatabaseUrl } from '../../../__tests__/pg-scratch.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Migration 0022 on a real Postgres, from both states a deployment can be in
|
|
16
|
+
* when it starts on this release: without `users.session_version` (every
|
|
17
|
+
* account starts at 0, so nobody is signed out by the upgrade), and with the
|
|
18
|
+
* column already there (the guarded `ADD COLUMN` changes nothing, so the
|
|
19
|
+
* generations it holds, and the sessions they honour, survive the start).
|
|
20
|
+
*
|
|
21
|
+
* Runs where `TEST_DATABASE_URL` names a server this role may create
|
|
22
|
+
* databases on, each test in a scratch database of its own.
|
|
23
|
+
*/
|
|
24
|
+
const KEY = randomBytes(32).toString('base64');
|
|
25
|
+
const open: Database[] = [];
|
|
26
|
+
const folders: string[] = [];
|
|
27
|
+
|
|
28
|
+
/** The core history as it stood BEFORE 0022: the packaged folder minus 0022, in a folder of this test's own. */
|
|
29
|
+
function historyBefore0022(): string {
|
|
30
|
+
const folder = mkdtempSync(path.join(tmpdir(), 'hexis-core-before-0022-'));
|
|
31
|
+
folders.push(folder);
|
|
32
|
+
cpSync(coreMigrationsDir(), folder, { recursive: true });
|
|
33
|
+
rmSync(path.join(folder, '0022_session_version.sql'));
|
|
34
|
+
rmSync(path.join(folder, 'meta', '0022_snapshot.json'));
|
|
35
|
+
const journalPath = path.join(folder, 'meta', '_journal.json');
|
|
36
|
+
const journal = JSON.parse(readFileSync(journalPath, 'utf8')) as { entries: { idx: number }[] };
|
|
37
|
+
journal.entries = journal.entries.filter((e) => e.idx < 22);
|
|
38
|
+
writeFileSync(journalPath, JSON.stringify(journal, null, 2));
|
|
39
|
+
return folder;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A database an older version left: started on the history to 0021, holding
|
|
44
|
+
* the accounts that version wrote, through SQL that names only the columns
|
|
45
|
+
* it knew. `withColumn` adds `session_version` by hand first, as a database
|
|
46
|
+
* that already has it would, and gives each account a generation of its own.
|
|
47
|
+
*/
|
|
48
|
+
async function olderDeployment(withColumn: boolean): Promise<Database> {
|
|
49
|
+
const db = createDb(await scratchDatabaseUrl('hexis_session_version'), { piiKey: KEY });
|
|
50
|
+
open.push(db);
|
|
51
|
+
await migrate(db, { migrationsFolder: historyBefore0022(), migrationsTable: '__drizzle_migrations_core' });
|
|
52
|
+
await runPiiEncryptionBackfill(db);
|
|
53
|
+
if (withColumn) await db.execute(sql`alter table users add column session_version integer default 0 not null`);
|
|
54
|
+
const seal = (plain: string) => new SealOnWrite(plain) as unknown as string;
|
|
55
|
+
const index = (plain: string) => new IndexOnWrite(plain) as unknown as string;
|
|
56
|
+
const account = (email: string, name: string) => sql`(${seal(email)}, ${index(email)}, ${seal(name)})`;
|
|
57
|
+
await db.execute(sql`
|
|
58
|
+
insert into users (email, email_bidx, name) values
|
|
59
|
+
${account('ada@example.com', 'Ada')},
|
|
60
|
+
${account('bo@example.com', 'Bo')}
|
|
61
|
+
`);
|
|
62
|
+
if (withColumn) {
|
|
63
|
+
await db.execute(sql`update users set session_version = 3 where email_bidx = ${index('ada@example.com')}`);
|
|
64
|
+
await db.execute(sql`update users set session_version = 7 where email_bidx = ${index('bo@example.com')}`);
|
|
65
|
+
}
|
|
66
|
+
return db;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** How many migrations the packaged history holds — what a fresh start records as applied. */
|
|
70
|
+
function packagedHistoryLength(): number {
|
|
71
|
+
const journal = JSON.parse(readFileSync(path.join(coreMigrationsDir(), 'meta', '_journal.json'), 'utf8')) as {
|
|
72
|
+
entries: unknown[];
|
|
73
|
+
};
|
|
74
|
+
return journal.entries.length;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Each account's generation, in a fixed order. */
|
|
78
|
+
const generations = async (db: Database) =>
|
|
79
|
+
(await db.execute(sql`select session_version from users order by session_version`)).rows.map(
|
|
80
|
+
(r) => (r as { session_version: number }).session_version,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
/** What the column is: its type, default and nullability, as Postgres reports them. */
|
|
84
|
+
const columnShape = async (db: Database) =>
|
|
85
|
+
(
|
|
86
|
+
await db.execute(sql`
|
|
87
|
+
select data_type, column_default, is_nullable from information_schema.columns
|
|
88
|
+
where table_schema = 'public' and table_name = 'users' and column_name = 'session_version'
|
|
89
|
+
`)
|
|
90
|
+
).rows;
|
|
91
|
+
|
|
92
|
+
const EXPECTED_SHAPE = [{ data_type: 'integer', column_default: '0', is_nullable: 'NO' }];
|
|
93
|
+
|
|
94
|
+
describe.skipIf(!PG_ADMIN_URL)('migration 0022 (users.session_version), on a real Postgres', () => {
|
|
95
|
+
afterAll(async () => {
|
|
96
|
+
for (const db of open.splice(0)) await closeDb(db);
|
|
97
|
+
for (const folder of folders.splice(0)) rmSync(folder, { recursive: true, force: true });
|
|
98
|
+
await dropScratchDatabases();
|
|
99
|
+
}, PG_TEST_TIMEOUT);
|
|
100
|
+
|
|
101
|
+
it(
|
|
102
|
+
'adds the column at 0 for every existing account, so no session is ended by the upgrade',
|
|
103
|
+
async () => {
|
|
104
|
+
const db = await olderDeployment(false);
|
|
105
|
+
expect(await columnShape(db)).toEqual([]);
|
|
106
|
+
|
|
107
|
+
await runCoreMigrations(db, coreMigrationsDir());
|
|
108
|
+
|
|
109
|
+
expect(await columnShape(db)).toEqual(EXPECTED_SHAPE);
|
|
110
|
+
expect(await generations(db)).toEqual([0, 0]);
|
|
111
|
+
},
|
|
112
|
+
PG_TEST_TIMEOUT,
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
it(
|
|
116
|
+
'finds the column already there and changes nothing, keeping every generation it holds',
|
|
117
|
+
async () => {
|
|
118
|
+
const db = await olderDeployment(true);
|
|
119
|
+
expect(await columnShape(db)).toEqual(EXPECTED_SHAPE);
|
|
120
|
+
|
|
121
|
+
await runCoreMigrations(db, coreMigrationsDir());
|
|
122
|
+
|
|
123
|
+
expect(await columnShape(db)).toEqual(EXPECTED_SHAPE);
|
|
124
|
+
expect(await generations(db)).toEqual([3, 7]);
|
|
125
|
+
// And 0022 is recorded as applied, so the next start does not try it
|
|
126
|
+
// again — judged against the packaged history's own length, so a later
|
|
127
|
+
// migration does not make this count stale.
|
|
128
|
+
const applied = await db.execute(sql`select count(*)::int as n from drizzle.__drizzle_migrations_core`);
|
|
129
|
+
expect((applied.rows[0] as { n: number }).n).toBe(packagedHistoryLength());
|
|
130
|
+
},
|
|
131
|
+
PG_TEST_TIMEOUT,
|
|
132
|
+
);
|
|
133
|
+
});
|