@bevel-software/platform-core-backend 0.34.0 → 0.36.1
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 -300
- 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 +16 -11
- package/agent-guide/new-knowledge-base.md +9 -8
- package/agent-guide/onboarding.md +111 -0
- package/agent-guide/skills.md +67 -42
- package/agent-guide/start-here.md +66 -0
- package/agent-guide/tool-manuals.md +133 -154
- package/agent-guide/tools.md +67 -0
- package/agent-guide/where-a-new-file-goes.md +45 -28
- package/dist/core/catalog-cache-invalidation.d.ts +2 -2
- package/dist/core/catalog-cache-invalidation.d.ts.map +1 -1
- package/dist/core/compose-embed.d.ts +2 -2
- package/dist/core/compose-embed.d.ts.map +1 -1
- package/dist/core/compose-embed.js +1 -1
- package/dist/core/compose-embed.js.map +1 -1
- package/dist/core/core-ports.d.ts +19 -4
- 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 +27 -25
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +25 -13
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +75 -60
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +4 -4
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +5 -5
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/core-config.d.ts +2 -2
- package/dist/core-config.js +3 -3
- package/dist/core-config.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +22 -0
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +3 -1
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +76 -1
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access-declarations.d.ts +2 -2
- package/dist/modules/access/access-declarations.d.ts.map +1 -1
- package/dist/modules/access/access-declarations.js +2 -2
- package/dist/modules/access/access-declarations.js.map +1 -1
- package/dist/modules/access/access-mutation.service.d.ts +2 -2
- package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
- package/dist/modules/access/access-mutation.service.js +2 -2
- package/dist/modules/access/access-mutation.service.js.map +1 -1
- package/dist/modules/access/access-requests.routes.d.ts +2 -2
- package/dist/modules/access/access-requests.routes.d.ts.map +1 -1
- package/dist/modules/access/access-requests.routes.js +5 -5
- package/dist/modules/access/access-requests.routes.js.map +1 -1
- package/dist/modules/access/access-requests.service.d.ts +2 -2
- package/dist/modules/access/access-requests.service.d.ts.map +1 -1
- package/dist/modules/access/access-requests.service.js +11 -11
- package/dist/modules/access/access-requests.service.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts +2 -2
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +32 -13
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/admin-locked-commit.d.ts +5 -5
- package/dist/modules/access/admin-locked-commit.d.ts.map +1 -1
- package/dist/modules/access/admin-locked-commit.js +7 -7
- package/dist/modules/access/admin-locked-commit.js.map +1 -1
- package/dist/modules/access/admin-route-helpers.d.ts +1 -1
- package/dist/modules/access/admin-route-helpers.js +5 -5
- package/dist/modules/access/admin-route-helpers.js.map +1 -1
- package/dist/modules/access/change-read-gate.d.ts +1 -1
- package/dist/modules/access/change-read-gate.js +1 -1
- package/dist/modules/access/groups-admin.service.d.ts +7 -7
- package/dist/modules/access/groups-admin.service.d.ts.map +1 -1
- package/dist/modules/access/groups-admin.service.js +6 -6
- package/dist/modules/access/groups-admin.service.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 +1 -1
- package/dist/modules/access/roles-admin.service.d.ts +7 -7
- package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
- package/dist/modules/access/roles-admin.service.js +8 -8
- package/dist/modules/access/roles-admin.service.js.map +1 -1
- package/dist/modules/access/synced-groups-committer.d.ts +4 -4
- package/dist/modules/access/synced-groups-committer.d.ts.map +1 -1
- package/dist/modules/access/synced-groups-committer.js +7 -7
- package/dist/modules/access/synced-groups-committer.js.map +1 -1
- package/dist/modules/access/user-access-removal.service.d.ts +6 -6
- package/dist/modules/access/user-access-removal.service.d.ts.map +1 -1
- package/dist/modules/access/user-access-removal.service.js +4 -4
- package/dist/modules/access/user-access-removal.service.js.map +1 -1
- package/dist/modules/access-model/access-errors.d.ts +4 -4
- package/dist/modules/access-model/access-errors.d.ts.map +1 -1
- package/dist/modules/access-model/access-errors.js +4 -4
- package/dist/modules/access-model/access-errors.js.map +1 -1
- package/dist/modules/access-model/access-grammar.d.ts +1 -1
- package/dist/modules/access-model/access-grammar.js +4 -4
- package/dist/modules/access-model/change-gate.d.ts +1 -1
- package/dist/modules/access-model/change-gate.js +1 -1
- package/dist/modules/access-model/roles-yaml-guard.d.ts +4 -4
- package/dist/modules/access-model/roles-yaml-guard.d.ts.map +1 -1
- package/dist/modules/access-model/roles-yaml-guard.js +4 -4
- package/dist/modules/access-model/roles-yaml-guard.js.map +1 -1
- package/dist/modules/agent-guide/agent-guide.d.ts +63 -18
- package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.js +144 -30
- 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 +45 -31
- package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -1
- package/dist/modules/agent-guide/guide-budget.d.ts +51 -0
- package/dist/modules/agent-guide/guide-budget.d.ts.map +1 -0
- package/dist/modules/agent-guide/guide-budget.js +71 -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/agent-instructions.routes.d.ts +12 -5
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.js +13 -6
- package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -1
- package/dist/modules/agent-instructions/compose.d.ts +15 -8
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
- package/dist/modules/agent-instructions/compose.js +28 -10
- package/dist/modules/agent-instructions/compose.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts +14 -6
- package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.js +60 -18
- package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
- package/dist/modules/auth/account-erasure.service.d.ts +36 -7
- package/dist/modules/auth/account-erasure.service.d.ts.map +1 -1
- package/dist/modules/auth/account-erasure.service.js +30 -10
- package/dist/modules/auth/account-erasure.service.js.map +1 -1
- package/dist/modules/auth/account-hooks.d.ts +20 -0
- package/dist/modules/auth/account-hooks.d.ts.map +1 -1
- package/dist/modules/auth/account-hooks.js +10 -0
- package/dist/modules/auth/account-hooks.js.map +1 -1
- package/dist/modules/auth/account-switch-lock.js +2 -2
- package/dist/modules/auth/account-switch-lock.js.map +1 -1
- package/dist/modules/auth/account.routes.d.ts +7 -2
- package/dist/modules/auth/account.routes.d.ts.map +1 -1
- package/dist/modules/auth/account.routes.js +23 -4
- package/dist/modules/auth/account.routes.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +1 -1
- package/dist/modules/auth/auth.service.js +4 -4
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/auth/index.d.ts +1 -0
- package/dist/modules/auth/index.d.ts.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.js +2 -2
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/diff/diff.routes.d.ts +3 -3
- package/dist/modules/diff/diff.routes.d.ts.map +1 -1
- package/dist/modules/diff/diff.routes.js +7 -7
- package/dist/modules/diff/diff.routes.js.map +1 -1
- package/dist/modules/embed/embed.routes.js +1 -1
- package/dist/modules/embed/embed.routes.js.map +1 -1
- package/dist/modules/embed/embed.service.d.ts +4 -4
- package/dist/modules/embed/embed.service.d.ts.map +1 -1
- package/dist/modules/embed/embed.service.js +18 -18
- package/dist/modules/embed/embed.service.js.map +1 -1
- package/dist/modules/kb-fs/branch-name.js +8 -8
- package/dist/modules/kb-fs/branch-name.js.map +1 -1
- package/dist/modules/kb-fs/file-change-notifier.d.ts +2 -2
- package/dist/modules/kb-fs/locking-filesystem.d.ts +9 -9
- package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.js +27 -27
- package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
- package/dist/modules/kb-fs/repo-path.js +5 -5
- package/dist/modules/kb-fs/repo-path.js.map +1 -1
- package/dist/modules/kb-fs/tmp-ref.d.ts +2 -2
- package/dist/modules/kb-fs/tmp-ref.d.ts.map +1 -1
- package/dist/modules/kb-fs/tmp-ref.js +2 -2
- package/dist/modules/kb-fs/tmp-ref.js.map +1 -1
- package/dist/modules/kb-sync/index.d.ts +1 -1
- package/dist/modules/kb-sync/index.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.interface.d.ts +3 -3
- package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.service.d.ts +3 -3
- package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.service.js +10 -10
- package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +11 -4
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +80 -53
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/onboarding/onboarding.contract.d.ts +1 -1
- package/dist/modules/onboarding/onboarding.routes.js +2 -2
- package/dist/modules/onboarding/onboarding.routes.js.map +1 -1
- package/dist/modules/onboarding/starter-pack.service.d.ts +4 -4
- package/dist/modules/onboarding/starter-pack.service.d.ts.map +1 -1
- package/dist/modules/onboarding/starter-pack.service.js +4 -4
- package/dist/modules/onboarding/starter-pack.service.js.map +1 -1
- package/dist/modules/plugins/join-request-jobs.service.d.ts +2 -2
- package/dist/modules/plugins/join-request-jobs.service.d.ts.map +1 -1
- package/dist/modules/plugins/join-request-jobs.service.js +7 -7
- package/dist/modules/plugins/join-request-jobs.service.js.map +1 -1
- package/dist/modules/plugins/join-requests.service.d.ts +3 -3
- package/dist/modules/plugins/join-requests.service.d.ts.map +1 -1
- package/dist/modules/plugins/join-requests.service.js +5 -5
- package/dist/modules/plugins/join-requests.service.js.map +1 -1
- package/dist/modules/plugins/plugin-rename.service.js +1 -1
- package/dist/modules/plugins/plugin-rename.service.js.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +2 -2
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +8 -8
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +87 -2
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.js +635 -74
- package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
- package/dist/modules/secrets-vault/index.d.ts +3 -2
- package/dist/modules/secrets-vault/index.d.ts.map +1 -1
- package/dist/modules/secrets-vault/index.js +2 -1
- package/dist/modules/secrets-vault/index.js.map +1 -1
- package/dist/modules/secrets-vault/managed-sign-in-erasure.d.ts +36 -0
- package/dist/modules/secrets-vault/managed-sign-in-erasure.d.ts.map +1 -0
- package/dist/modules/secrets-vault/managed-sign-in-erasure.js +37 -0
- package/dist/modules/secrets-vault/managed-sign-in-erasure.js.map +1 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +205 -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 +7 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.js +206 -45
- package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
- package/dist/modules/settings/connection-check.js +1 -1
- package/dist/modules/settings/connection-check.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +1 -1
- package/dist/modules/settings/git-root-folders.js +1 -1
- package/dist/modules/settings/git-root-folders.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/settings/setup.routes.d.ts +1 -1
- package/dist/modules/settings/setup.routes.js +1 -1
- package/dist/modules/skills/pending-skills.service.d.ts +3 -3
- package/dist/modules/skills/pending-skills.service.d.ts.map +1 -1
- package/dist/modules/skills/pending-skills.service.js +4 -4
- package/dist/modules/skills/pending-skills.service.js.map +1 -1
- package/dist/modules/skills/skill-access-requests.routes.d.ts +2 -2
- package/dist/modules/skills/skill-access-requests.routes.d.ts.map +1 -1
- package/dist/modules/skills/skill-access-requests.routes.js +5 -5
- package/dist/modules/skills/skill-access-requests.routes.js.map +1 -1
- package/dist/modules/skills/skills.tools.js +1 -1
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-helpers/tool-context.d.ts +4 -4
- package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-context.js +2 -2
- 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.js +2 -2
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts +4 -4
- package/dist/modules/tool-helpers/tool.contract.d.ts.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/mcp-server-edit.service.js +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -1
- package/dist/modules/tool-manuals/pending-tools.service.d.ts +3 -3
- package/dist/modules/tool-manuals/pending-tools.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/pending-tools.service.js +4 -4
- package/dist/modules/tool-manuals/pending-tools.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-delete.service.js +1 -1
- package/dist/modules/tool-manuals/tool-delete.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +70 -3
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +18 -8
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +166 -49
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +11 -2
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +66 -24
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/version-control/agent-tools/change-request-read-shape.d.ts.map +1 -0
- package/dist/modules/version-control/agent-tools/change-request-read-shape.js.map +1 -0
- package/dist/modules/version-control/agent-tools/change-request-read.tools.d.ts.map +1 -0
- package/dist/modules/{workflow → version-control}/agent-tools/change-request-read.tools.js +2 -2
- package/dist/modules/version-control/agent-tools/change-request-read.tools.js.map +1 -0
- package/dist/modules/version-control/agent-tools/change-request-summary.d.ts.map +1 -0
- package/dist/modules/version-control/agent-tools/change-request-summary.js.map +1 -0
- package/dist/modules/{workflow/agent-tools/workflow.tools.d.ts → version-control/agent-tools/version-control.tools.d.ts} +4 -4
- package/dist/modules/version-control/agent-tools/version-control.tools.d.ts.map +1 -0
- package/dist/modules/{workflow/agent-tools/workflow.tools.js → version-control/agent-tools/version-control.tools.js} +53 -49
- package/dist/modules/version-control/agent-tools/version-control.tools.js.map +1 -0
- package/dist/modules/{workflow → version-control}/change-request-hooks.d.ts +1 -1
- package/dist/modules/version-control/change-request-hooks.d.ts.map +1 -0
- package/dist/modules/version-control/change-request-hooks.js.map +1 -0
- package/dist/modules/version-control/change-request-review/approval-lock.d.ts.map +1 -0
- package/dist/modules/version-control/change-request-review/approval-lock.js.map +1 -0
- package/dist/modules/{workflow/review-workflow/review-workflow.interface.d.ts → version-control/change-request-review/change-request-review.interface.d.ts} +2 -2
- package/dist/modules/version-control/change-request-review/change-request-review.interface.d.ts.map +1 -0
- package/dist/modules/version-control/change-request-review/change-request-review.interface.js +2 -0
- package/dist/modules/version-control/change-request-review/change-request-review.interface.js.map +1 -0
- package/dist/modules/{workflow/review-workflow/review-workflow.service.d.ts → version-control/change-request-review/change-request-review.service.d.ts} +3 -3
- package/dist/modules/version-control/change-request-review/change-request-review.service.d.ts.map +1 -0
- package/dist/modules/{workflow/review-workflow/review-workflow.service.js → version-control/change-request-review/change-request-review.service.js} +26 -26
- package/dist/modules/version-control/change-request-review/change-request-review.service.js.map +1 -0
- package/dist/modules/{workflow → version-control}/event-bus.d.ts +8 -8
- package/dist/modules/version-control/event-bus.d.ts.map +1 -0
- package/dist/modules/{workflow → version-control}/event-bus.js +4 -4
- package/dist/modules/version-control/event-bus.js.map +1 -0
- package/dist/modules/{workflow → version-control}/events-delivery.d.ts +4 -4
- package/dist/modules/version-control/events-delivery.d.ts.map +1 -0
- package/dist/modules/version-control/events-delivery.js.map +1 -0
- package/dist/modules/{workflow → version-control}/events.routes.d.ts +4 -4
- package/dist/modules/version-control/events.routes.d.ts.map +1 -0
- package/dist/modules/version-control/events.routes.js.map +1 -0
- package/dist/modules/{workflow → version-control}/file-lock.service.d.ts +2 -2
- package/dist/modules/version-control/file-lock.service.d.ts.map +1 -0
- package/dist/modules/{workflow → version-control}/file-lock.service.js +6 -6
- package/dist/modules/version-control/file-lock.service.js.map +1 -0
- package/dist/modules/version-control/git/change-request-link.d.ts.map +1 -0
- package/dist/modules/version-control/git/change-request-link.js.map +1 -0
- package/dist/modules/version-control/git/git-version.d.ts.map +1 -0
- package/dist/modules/version-control/git/git-version.js.map +1 -0
- package/dist/modules/{workflow → version-control}/git/git.service.d.ts +8 -8
- package/dist/modules/version-control/git/git.service.d.ts.map +1 -0
- package/dist/modules/{workflow → version-control}/git/git.service.js +53 -53
- package/dist/modules/version-control/git/git.service.js.map +1 -0
- package/dist/modules/version-control/git/merge-commit.d.ts.map +1 -0
- package/dist/modules/version-control/git/merge-commit.js.map +1 -0
- package/dist/modules/version-control/git/node-git-runner.d.ts.map +1 -0
- package/dist/modules/{workflow → version-control}/git/node-git-runner.js +1 -1
- package/dist/modules/version-control/git/node-git-runner.js.map +1 -0
- package/dist/modules/{workflow → version-control}/git/pull-request.service.d.ts +5 -5
- package/dist/modules/version-control/git/pull-request.service.d.ts.map +1 -0
- package/dist/modules/{workflow → version-control}/git/pull-request.service.js +10 -10
- package/dist/modules/version-control/git/pull-request.service.js.map +1 -0
- package/dist/modules/{workflow → version-control}/index.d.ts +6 -5
- package/dist/modules/version-control/index.d.ts.map +1 -0
- package/dist/modules/{workflow → version-control}/index.js +3 -2
- package/dist/modules/version-control/index.js.map +1 -0
- package/dist/modules/{workflow → version-control}/pending-commits.service.d.ts +2 -2
- package/dist/modules/version-control/pending-commits.service.d.ts.map +1 -0
- package/dist/modules/{workflow → version-control}/pending-commits.service.js +2 -2
- package/dist/modules/version-control/pending-commits.service.js.map +1 -0
- package/dist/modules/{workflow → version-control}/pending-commits.worker.d.ts +5 -5
- package/dist/modules/version-control/pending-commits.worker.d.ts.map +1 -0
- package/dist/modules/{workflow → version-control}/pending-commits.worker.js +3 -3
- package/dist/modules/version-control/pending-commits.worker.js.map +1 -0
- package/dist/modules/{workflow → version-control}/read-on-branch.d.ts +2 -2
- package/dist/modules/version-control/read-on-branch.d.ts.map +1 -0
- package/dist/modules/{workflow → version-control}/read-on-branch.js +2 -2
- package/dist/modules/version-control/read-on-branch.js.map +1 -0
- package/dist/modules/version-control/recovery-bot.d.ts.map +1 -0
- package/dist/modules/version-control/recovery-bot.js.map +1 -0
- package/dist/modules/version-control/sanitize-error.d.ts.map +1 -0
- package/dist/modules/version-control/sanitize-error.js.map +1 -0
- package/dist/modules/{workflow/workflow-hooks.d.ts → version-control/version-control-hooks.d.ts} +4 -4
- package/dist/modules/version-control/version-control-hooks.d.ts.map +1 -0
- package/dist/modules/{workflow/workflow-hooks.js → version-control/version-control-hooks.js} +3 -3
- package/dist/modules/version-control/version-control-hooks.js.map +1 -0
- package/dist/modules/{workflow/workflow.routes.d.ts → version-control/version-control.routes.d.ts} +6 -5
- package/dist/modules/version-control/version-control.routes.d.ts.map +1 -0
- package/dist/modules/{workflow/workflow.routes.js → version-control/version-control.routes.js} +61 -60
- package/dist/modules/version-control/version-control.routes.js.map +1 -0
- package/dist/modules/{workflow/workflow.service.d.ts → version-control/version-control.service.d.ts} +26 -31
- package/dist/modules/version-control/version-control.service.d.ts.map +1 -0
- package/dist/modules/{workflow/workflow.service.js → version-control/version-control.service.js} +89 -94
- package/dist/modules/version-control/version-control.service.js.map +1 -0
- package/dist/modules/workspace/agent-access.gate.d.ts +6 -6
- package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -1
- package/dist/modules/workspace/agent-access.gate.js +2 -2
- package/dist/modules/workspace/agent-access.gate.js.map +1 -1
- package/dist/modules/workspace/git-internals.middleware.d.ts +1 -1
- package/dist/modules/workspace/git-internals.middleware.js +2 -2
- package/dist/modules/workspace/kb-repository.js +1 -1
- package/dist/modules/workspace/kb-repository.js.map +1 -1
- package/dist/modules/workspace/startup/kb-git.d.ts +1 -1
- package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-git.js +1 -1
- package/dist/modules/workspace/startup/kb-git.js.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts +2 -2
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts +2 -2
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.js +2 -2
- package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.d.ts +17 -4
- package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.js +8 -5
- package/dist/modules/workspace/startup/steps/template-source.js.map +1 -1
- package/dist/modules/workspace/workspace-tmp.js +3 -3
- package/dist/modules/workspace/workspace-tmp.js.map +1 -1
- package/dist/modules/workspace/workspace.routes.d.ts +4 -4
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +22 -22
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +6 -6
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +11 -11
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +5 -3
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +85 -30
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/shared/canonical-file-identity.d.ts +2 -2
- package/dist/shared/canonical-file-identity.js +5 -5
- package/dist/shared/canonical-file-identity.js.map +1 -1
- package/dist/shared/domain-errors.d.ts +39 -39
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +44 -44
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/git.contract.d.ts +1 -1
- package/dist/shared/git.contract.js +1 -1
- package/dist/shared/http-errors.d.ts +3 -3
- package/dist/shared/http-errors.d.ts.map +1 -1
- package/dist/shared/http-errors.js +1 -1
- package/dist/shared/http-errors.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/pending-proposals.d.ts +3 -3
- package/dist/shared/pending-proposals.d.ts.map +1 -1
- package/dist/shared/pending-proposals.js +2 -2
- package/dist/shared/pending-proposals.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/dist/tenancy/static-tenant-source.js +1 -1
- package/dist/tenancy/static-tenant-source.js.map +1 -1
- package/migrations/0012_agent_audit.sql +40 -40
- package/migrations/0013_agent_identity_and_key_soft_delete.sql +29 -29
- package/migrations/meta/0012_snapshot.json +2246 -2246
- package/migrations/meta/0013_snapshot.json +2258 -2258
- package/package.json +5 -4
- package/src/__tests__/account-admin.ts +27 -0
- package/src/__tests__/hexis-catalog.ts +144 -0
- package/src/__tests__/kb-context.ts +30 -30
- package/src/__tests__/kb-layout-config.test.ts +21 -0
- package/src/__tests__/open-change-gate.ts +1 -1
- package/src/__tests__/real-disk.ts +21 -0
- package/src/__tests__/tokens.ts +19 -0
- package/src/__tests__/tool-handler.ts +15 -0
- package/src/__tests__/tool-manuals.ts +32 -0
- package/src/__tests__/tool-wiring.ts +31 -0
- package/src/__tests__/workspace-service.ts +1 -1
- package/src/core/__tests__/catalog-cache-invalidation.test.ts +3 -3
- package/src/core/__tests__/compose-embed.test.ts +1 -1
- package/src/core/__tests__/damaged-document.tools.test.ts +3 -3
- package/src/core/__tests__/erasure-slices-wired-once.test.ts +46 -0
- package/src/core/__tests__/gated-boot.test.ts +2 -2
- package/src/core/__tests__/lifecycle.test.ts +9 -9
- package/src/core/__tests__/oauth-sign-in-real-file.test.ts +179 -0
- package/src/core/catalog-cache-invalidation.ts +2 -2
- package/src/core/compose-embed.ts +3 -3
- package/src/core/core-ports.ts +19 -4
- package/src/core/create-core-server.ts +40 -26
- package/src/core/create-core-services.ts +92 -66
- package/src/core/lifecycle.ts +9 -9
- package/src/core-config.ts +3 -3
- package/src/index.ts +5 -5
- package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +4 -4
- package/src/modules/access/__tests__/access-own-read-grant.test.ts +14 -14
- package/src/modules/access/__tests__/access-requests.routes.test.ts +20 -20
- package/src/modules/access/__tests__/access.routes.admins.test.ts +4 -4
- package/src/modules/access/__tests__/access.routes.batch.test.ts +4 -4
- package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +6 -6
- package/src/modules/access/__tests__/access.routes.group-grant.test.ts +6 -6
- package/src/modules/access/__tests__/access.routes.overrides.test.ts +5 -5
- package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +6 -6
- package/src/modules/access/__tests__/access.routes.prospective.test.ts +54 -14
- package/src/modules/access/__tests__/access.routes.revoke.test.ts +6 -6
- package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +6 -6
- package/src/modules/access/__tests__/admin-locked-commit.test.ts +6 -6
- package/src/modules/access/__tests__/admin-route-helpers.test.ts +5 -5
- package/src/modules/access/__tests__/groups-admin.service.test.ts +27 -27
- package/src/modules/access/__tests__/holder-changes.test.ts +127 -0
- package/src/modules/access/__tests__/roles-admin.security.test.ts +14 -13
- package/src/modules/access/__tests__/roles-admin.service.test.ts +27 -27
- package/src/modules/access/__tests__/roles-capabilities.test.ts +19 -19
- package/src/modules/access/__tests__/roles.routes.test.ts +6 -6
- package/src/modules/access/__tests__/synced-groups-committer.test.ts +13 -13
- package/src/modules/access/__tests__/user-access-removal.service.test.ts +29 -29
- package/src/modules/access/access-control.interface.ts +26 -0
- package/src/modules/access/access-control.service.ts +93 -1
- package/src/modules/access/access-declarations.ts +2 -2
- package/src/modules/access/access-mutation.service.ts +2 -2
- package/src/modules/access/access-requests.routes.ts +7 -7
- package/src/modules/access/access-requests.service.ts +13 -13
- package/src/modules/access/access.routes.ts +34 -15
- package/src/modules/access/admin-locked-commit.ts +11 -11
- package/src/modules/access/admin-route-helpers.ts +5 -5
- package/src/modules/access/change-read-gate.ts +1 -1
- package/src/modules/access/groups-admin.service.ts +7 -7
- package/src/modules/access/index.ts +1 -1
- package/src/modules/access/reference-scan.ts +1 -1
- package/src/modules/access/roles-admin.service.ts +9 -9
- package/src/modules/access/synced-groups-committer.ts +11 -11
- package/src/modules/access/user-access-removal.service.ts +7 -7
- package/src/modules/access-model/__tests__/access-grammar.test.ts +2 -2
- package/src/modules/access-model/access-errors.ts +4 -4
- package/src/modules/access-model/access-grammar.ts +4 -4
- package/src/modules/access-model/change-gate.ts +1 -1
- package/src/modules/access-model/roles-yaml-guard.ts +4 -4
- package/src/modules/agent-guide/__tests__/agent-guide.test.ts +98 -29
- package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +39 -12
- 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 +49 -31
- package/src/modules/agent-guide/agent-guide.ts +165 -32
- package/src/modules/agent-guide/guide-budget.ts +77 -0
- package/src/modules/agent-guide/index.ts +12 -0
- package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +4 -5
- package/src/modules/agent-instructions/__tests__/compose.test.ts +1 -1
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +117 -11
- package/src/modules/agent-instructions/agent-instructions.routes.ts +12 -5
- package/src/modules/agent-instructions/compose.ts +28 -9
- package/src/modules/agent-instructions/shared-file-rules.ts +62 -18
- package/src/modules/agent-scope/__tests__/agent-surfaces.connect-as.test.ts +1 -1
- package/src/modules/audit/__tests__/agent-rest-audit.middleware.test.ts +113 -113
- package/src/modules/audit/__tests__/event-classifier.test.ts +121 -121
- package/src/modules/audit/event-classifier.ts +155 -155
- package/src/modules/auth/__tests__/account-deactivation.test.ts +1 -1
- package/src/modules/auth/__tests__/account-erasure.approval-gate.test.ts +4 -4
- package/src/modules/auth/__tests__/account-erasure.participants.test.ts +56 -0
- package/src/modules/auth/__tests__/account-switch-lock.pg.test.ts +2 -2
- package/src/modules/auth/__tests__/account-switch-lock.test.ts +2 -2
- package/src/modules/auth/__tests__/account.routes.test.ts +73 -11
- package/src/modules/auth/__tests__/auth.service.test.ts +1 -1
- package/src/modules/auth/account-erasure.service.ts +27 -9
- package/src/modules/auth/account-hooks.ts +34 -0
- package/src/modules/auth/account-switch-lock.ts +2 -2
- package/src/modules/auth/account.routes.ts +23 -5
- package/src/modules/auth/auth.service.ts +4 -4
- package/src/modules/auth/index.ts +1 -0
- 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__/advisory-lock.test.ts +169 -169
- package/src/modules/database/__tests__/migrations-unqualified.test.ts +32 -32
- package/src/modules/database/core-schema.ts +2 -2
- package/src/modules/diff/__tests__/diff.routes.read-gate.test.ts +2 -2
- package/src/modules/diff/__tests__/diff.routes.rejectPathsLocked.test.ts +12 -12
- package/src/modules/diff/diff.routes.ts +8 -8
- package/src/modules/embed/__tests__/embed.service.lock.test.ts +7 -7
- package/src/modules/embed/__tests__/embed.service.test.ts +42 -42
- package/src/modules/embed/__tests__/embed.tools.test.ts +1 -1
- package/src/modules/embed/embed.routes.ts +1 -1
- package/src/modules/embed/embed.service.ts +17 -17
- package/src/modules/kb-fs/__tests__/branch-name.test.ts +2 -2
- package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +260 -260
- package/src/modules/kb-fs/__tests__/move-with-edits.test.ts +34 -34
- package/src/modules/kb-fs/__tests__/repo-path.test.ts +8 -8
- package/src/modules/kb-fs/branch-name.ts +8 -8
- package/src/modules/kb-fs/file-change-notifier.ts +2 -2
- package/src/modules/kb-fs/locking-filesystem.ts +30 -30
- package/src/modules/kb-fs/repo-path.ts +5 -5
- package/src/modules/kb-fs/tmp-ref.ts +3 -3
- package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +36 -36
- package/src/modules/kb-sync/index.ts +1 -1
- package/src/modules/kb-sync/kb-sync.interface.ts +3 -3
- package/src/modules/kb-sync/kb-sync.service.ts +9 -9
- package/src/modules/marketplace/__tests__/github-facade.test.ts +1 -1
- package/src/modules/marketplace/__tests__/marketplace-git.test.ts +1 -1
- package/src/modules/mcp/__tests__/mcp-proxy-helpers.test.ts +4 -2
- package/src/modules/mcp/__tests__/mcp.e2e.test.ts +69 -8
- package/src/modules/mcp/__tests__/mcp.service.test.ts +91 -13
- package/src/modules/mcp/__tests__/read-only-hint.pg.test.ts +194 -0
- package/src/modules/mcp/mcp.service.ts +93 -62
- package/src/modules/onboarding/__tests__/starter-pack.service.test.ts +55 -55
- package/src/modules/onboarding/onboarding.contract.ts +1 -1
- package/src/modules/onboarding/onboarding.routes.ts +2 -2
- package/src/modules/onboarding/starter-pack.service.ts +6 -6
- package/src/modules/plugins/__tests__/bundle-dialect.e2e.test.ts +1 -1
- package/src/modules/plugins/__tests__/compile-marketplace.test.ts +1 -1
- package/src/modules/plugins/__tests__/join-request-jobs.service.test.ts +43 -43
- package/src/modules/plugins/__tests__/join-requests.service.test.ts +19 -19
- package/src/modules/plugins/__tests__/join-requests.settlement.test.ts +26 -26
- package/src/modules/plugins/__tests__/plugins.routes.test.ts +25 -25
- package/src/modules/plugins/join-request-jobs.service.ts +10 -10
- package/src/modules/plugins/join-requests.service.ts +4 -4
- package/src/modules/plugins/plugin-rename.service.ts +1 -1
- package/src/modules/plugins/plugins.routes.ts +9 -9
- package/src/modules/secrets-vault/__tests__/db-secrets-vault.managed.test.ts +773 -0
- package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +458 -4
- package/src/modules/secrets-vault/__tests__/managed-sign-in.route.test.ts +351 -0
- 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 +728 -118
- package/src/modules/secrets-vault/index.ts +8 -1
- package/src/modules/secrets-vault/managed-sign-in-erasure.ts +41 -0
- package/src/modules/secrets-vault/secrets-variable-loader.ts +134 -134
- package/src/modules/secrets-vault/secrets-vault.contract.ts +213 -11
- package/src/modules/secrets-vault/secrets-vault.routes.ts +244 -45
- package/src/modules/settings/__tests__/repository-source.test.ts +1 -1
- package/src/modules/settings/__tests__/setup.routes.layout-phase.test.ts +1 -1
- package/src/modules/settings/__tests__/setup.routes.managed-phase.test.ts +1 -1
- package/src/modules/settings/connection-check.ts +1 -1
- package/src/modules/settings/deployment-settings.service.ts +1 -1
- package/src/modules/settings/git-root-folders.ts +1 -1
- package/src/modules/settings/oidc-check.ts +5 -18
- package/src/modules/settings/setup.routes.ts +1 -1
- package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +3 -3
- package/src/modules/skills/__tests__/branch-skills.tools.test.ts +1 -1
- package/src/modules/skills/__tests__/pending-skills.service.test.ts +8 -8
- package/src/modules/skills/__tests__/skill-access-requests.routes.test.ts +18 -18
- package/src/modules/skills/__tests__/skills.service.test.ts +3 -3
- package/src/modules/skills/pending-skills.service.ts +3 -3
- package/src/modules/skills/skill-access-requests.routes.ts +7 -7
- package/src/modules/skills/skills.tools.ts +1 -1
- package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +22 -14
- package/src/modules/tool-helpers/__tests__/branch-declaration.test.ts +3 -3
- package/src/modules/tool-helpers/__tests__/can-write.tools.test.ts +3 -3
- package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +4 -4
- package/src/modules/tool-helpers/__tests__/route-tool-schemas.catalog.test.ts +38 -0
- package/src/modules/tool-helpers/__tests__/tools-surface.test.ts +3 -3
- package/src/modules/tool-helpers/__tests__/validate-token.test.ts +1 -1
- package/src/modules/tool-helpers/tool-context.ts +6 -6
- package/src/modules/tool-helpers/tool-def.ts +1 -1
- package/src/modules/tool-helpers/tool-handler.ts +2 -2
- package/src/modules/tool-helpers/tool.contract.ts +4 -4
- package/src/modules/tool-manuals/__tests__/pending-tools.service.test.ts +9 -9
- package/src/modules/tool-manuals/__tests__/tool-manuals.managed-sign-in.test.ts +89 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +197 -11
- package/src/modules/tool-manuals/__tests__/tool-manuals.restart.test.ts +1 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +132 -6
- package/src/modules/tool-manuals/mcp-json-discovery.ts +73 -34
- package/src/modules/tool-manuals/mcp-server-edit.service.ts +1 -1
- package/src/modules/tool-manuals/pending-tools.service.ts +3 -3
- package/src/modules/tool-manuals/tool-delete.service.ts +1 -1
- package/src/modules/tool-manuals/tool-manuals.contract.ts +63 -3
- package/src/modules/tool-manuals/tool-manuals.service.ts +191 -51
- package/src/modules/tool-manuals/tool-manuals.tools.ts +93 -28
- package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +4 -4
- package/src/modules/tool-registry/__tests__/read-only-hints.test.ts +3 -3
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +12 -119
- package/src/modules/{workflow → version-control}/__tests__/apply-failure.test.ts +54 -54
- package/src/modules/{workflow → version-control}/__tests__/event-bus.test.ts +27 -27
- package/src/modules/{workflow → version-control}/__tests__/events-delivery.test.ts +37 -37
- package/src/modules/{workflow → version-control}/__tests__/file-lock.service.deletion-gate.test.ts +2 -2
- package/src/modules/{workflow → version-control}/__tests__/file-lock.service.path-identity.test.ts +8 -8
- package/src/modules/{workflow → version-control}/__tests__/merge-announces-tree.test.ts +11 -11
- package/src/modules/{workflow → version-control}/__tests__/pending-commits.worker.concurrency.test.ts +9 -9
- package/src/modules/{workflow → version-control}/__tests__/pending-commits.worker.test.ts +25 -25
- package/src/modules/{workflow → version-control}/__tests__/preserve-roles-yaml.test.ts +42 -42
- package/src/modules/version-control/__tests__/public-surface.test.ts +114 -0
- package/src/modules/{workflow/__tests__/workflow.routes.folder-proposals.test.ts → version-control/__tests__/version-control.routes.folder-proposals.test.ts} +18 -18
- package/src/modules/{workflow/__tests__/workflow.routes.fork-point-file.test.ts → version-control/__tests__/version-control.routes.fork-point-file.test.ts} +19 -19
- package/src/modules/{workflow/__tests__/workflow.routes.history-read-gate.test.ts → version-control/__tests__/version-control.routes.history-read-gate.test.ts} +13 -13
- package/src/modules/{workflow/__tests__/workflow.routes.lock-path.test.ts → version-control/__tests__/version-control.routes.lock-path.test.ts} +18 -18
- package/src/modules/{workflow/__tests__/workflow.routes.open-change-request.test.ts → version-control/__tests__/version-control.routes.open-change-request.test.ts} +7 -7
- package/src/modules/{workflow/__tests__/workflow.routes.push-refused.test.ts → version-control/__tests__/version-control.routes.push-refused.test.ts} +14 -14
- package/src/modules/{workflow/__tests__/workflow.service.branch-in-use.test.ts → version-control/__tests__/version-control.service.branch-in-use.test.ts} +6 -6
- package/src/modules/{workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts → version-control/__tests__/version-control.service.commitFileWhileLocked.test.ts} +14 -14
- package/src/modules/{workflow/__tests__/workflow.service.delete-branch.test.ts → version-control/__tests__/version-control.service.delete-branch.test.ts} +14 -14
- package/src/modules/{workflow/__tests__/workflow.service.deleted-branch-sweep.test.ts → version-control/__tests__/version-control.service.deleted-branch-sweep.test.ts} +4 -4
- package/src/modules/{workflow/__tests__/workflow.service.facade.test.ts → version-control/__tests__/version-control.service.facade.test.ts} +97 -99
- package/src/modules/{workflow/__tests__/workflow.service.folder-proposals.test.ts → version-control/__tests__/version-control.service.folder-proposals.test.ts} +7 -7
- package/src/modules/{workflow/__tests__/workflow.service.platform-restore.test.ts → version-control/__tests__/version-control.service.platform-restore.test.ts} +6 -6
- package/src/modules/{workflow/__tests__/workflow.service.read-gate.test.ts → version-control/__tests__/version-control.service.read-gate.test.ts} +9 -9
- package/src/modules/{workflow/__tests__/workflow.service.releaseLock.test.ts → version-control/__tests__/version-control.service.releaseLock.test.ts} +42 -42
- package/src/modules/{workflow/__tests__/workflow.service.repository-replaced.test.ts → version-control/__tests__/version-control.service.repository-replaced.test.ts} +4 -4
- package/src/modules/{workflow/__tests__/workflow.service.sync.test.ts → version-control/__tests__/version-control.service.sync.test.ts} +17 -17
- package/src/modules/{workflow/__tests__/workflow.service.update-change-request.pg.test.ts → version-control/__tests__/version-control.service.update-change-request.pg.test.ts} +6 -6
- package/src/modules/{workflow/__tests__/workflow.service.update-change-request.test.ts → version-control/__tests__/version-control.service.update-change-request.test.ts} +11 -11
- package/src/modules/{workflow/__tests__/workflow.service.update-from-target.test.ts → version-control/__tests__/version-control.service.update-from-target.test.ts} +22 -22
- package/src/modules/{workflow → version-control}/agent-tools/__tests__/change-request-read.tools.test.ts +3 -3
- package/src/modules/{workflow → version-control}/agent-tools/__tests__/update-change-request.parity.test.ts +16 -16
- package/src/modules/{workflow/agent-tools/__tests__/workflow.tools.test.ts → version-control/agent-tools/__tests__/version-control.tools.test.ts} +9 -9
- package/src/modules/{workflow → version-control}/agent-tools/change-request-read.tools.ts +2 -2
- package/src/modules/{workflow/agent-tools/workflow.tools.ts → version-control/agent-tools/version-control.tools.ts} +52 -48
- package/src/modules/{workflow → version-control}/change-request-hooks.ts +1 -1
- package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/approval-states.test.ts +5 -5
- package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/cancel-pr.test.ts +8 -8
- package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/carry-approvals-forward.test.ts +6 -6
- package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/erase-approver.test.ts +5 -5
- package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/merge-gate.test.ts +3 -3
- package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/merge-records-own-commit.test.ts +3 -3
- package/src/modules/{workflow/review-workflow/review-workflow.interface.ts → version-control/change-request-review/change-request-review.interface.ts} +1 -1
- package/src/modules/{workflow/review-workflow/review-workflow.service.ts → version-control/change-request-review/change-request-review.service.ts} +28 -28
- package/src/modules/{workflow → version-control}/event-bus.ts +14 -14
- package/src/modules/{workflow → version-control}/events-delivery.ts +11 -11
- package/src/modules/{workflow → version-control}/events.routes.ts +5 -5
- package/src/modules/{workflow → version-control}/file-lock.service.ts +6 -6
- package/src/modules/{workflow → version-control}/git/__tests__/git-test-helpers.ts +3 -3
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.accessGating.test.ts +5 -5
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.appliedChange.test.ts +4 -4
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.changedFilesForPr.test.ts +16 -16
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.changedPathsMemo.test.ts +2 -2
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.commitChanges.test.ts +3 -3
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.commitFile.strayPath.test.ts +10 -10
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.commitFile.test.ts +3 -3
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.createBranch.test.ts +3 -3
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.deleteBranch.test.ts +7 -7
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.diffFileAtCommit.test.ts +9 -9
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.diffFileBetweenBranches.test.ts +23 -23
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.fileBytesAtCommit.test.ts +7 -7
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.fileContentsAtCommit.test.ts +3 -3
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.forkPoint.test.ts +2 -2
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.history-guards.test.ts +2 -2
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.list-branches.test.ts +12 -12
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.listTrackedFiles.test.ts +2 -2
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.logForFile.test.ts +6 -6
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.mergeChangeRequest.test.ts +20 -20
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.mergeIntoBranch.test.ts +16 -16
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.pendingChanges.test.ts +2 -2
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.postMergePull.test.ts +5 -5
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.prFetchFailure.test.ts +2 -2
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.pull.test.ts +12 -12
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.readFileAtRef.test.ts +3 -3
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.revertPathsAndPush.test.ts +2 -2
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.status.test.ts +4 -4
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.syncFromRemote.test.ts +8 -8
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.worktrees.test.ts +4 -4
- package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.list-fetch.test.ts +2 -2
- package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.test.ts +6 -6
- package/src/modules/{workflow → version-control}/git/git.service.ts +57 -57
- package/src/modules/{workflow → version-control}/git/node-git-runner.ts +1 -1
- package/src/modules/{workflow → version-control}/git/pull-request.service.ts +12 -12
- package/src/modules/{workflow → version-control}/index.ts +6 -5
- package/src/modules/{workflow → version-control}/pending-commits.service.ts +2 -2
- package/src/modules/{workflow → version-control}/pending-commits.worker.ts +8 -8
- package/src/modules/{workflow → version-control}/read-on-branch.ts +2 -2
- package/src/modules/{workflow/workflow-hooks.ts → version-control/version-control-hooks.ts} +3 -3
- package/src/modules/{workflow/workflow.routes.ts → version-control/version-control.routes.ts} +64 -63
- package/src/modules/{workflow/workflow.service.ts → version-control/version-control.service.ts} +92 -97
- package/src/modules/workspace/__tests__/agent-access.gate.test.ts +8 -8
- package/src/modules/workspace/__tests__/agent-downloads.test.ts +13 -16
- package/src/modules/workspace/__tests__/agent-uploads.test.ts +11 -12
- package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +11 -16
- package/src/modules/workspace/__tests__/file-stat-access.test.ts +3 -3
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +27 -27
- package/src/modules/workspace/__tests__/kb-repository.test.ts +1 -1
- package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +3 -3
- package/src/modules/workspace/__tests__/start-session.default-branch.test.ts +152 -0
- package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +6 -6
- package/src/modules/workspace/__tests__/workspace-tmp.tools.test.ts +13 -14
- package/src/modules/workspace/__tests__/workspace.routes.at-ref.test.ts +6 -6
- package/src/modules/workspace/__tests__/workspace.routes.branch-errors.test.ts +4 -4
- package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +6 -6
- package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +8 -8
- package/src/modules/workspace/__tests__/workspace.routes.download-implies-read.test.ts +4 -4
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +6 -6
- package/src/modules/workspace/__tests__/workspace.routes.file-writes.test.ts +6 -6
- package/src/modules/workspace/__tests__/workspace.routes.folder-zip-access.test.ts +4 -4
- package/src/modules/workspace/__tests__/workspace.routes.folders.test.ts +6 -6
- package/src/modules/workspace/__tests__/workspace.routes.move-platform-files.test.ts +7 -7
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +13 -13
- package/src/modules/workspace/__tests__/workspace.service.any-workspace-credentials.test.ts +1 -1
- package/src/modules/workspace/__tests__/workspace.service.replaced-repository.test.ts +1 -1
- package/src/modules/workspace/__tests__/workspace.service.worktrees.test.ts +1 -1
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +12 -14
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +5 -5
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +182 -75
- package/src/modules/workspace/agent-access.gate.ts +6 -6
- package/src/modules/workspace/git-internals.middleware.ts +2 -2
- package/src/modules/workspace/kb-repository.ts +1 -1
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.degraded.test.ts +1 -1
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +1 -1
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.worktrees.test.ts +1 -1
- package/src/modules/workspace/startup/kb-git.ts +1 -1
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +94 -70
- package/src/modules/workspace/startup/steps/seed-tree.ts +4 -5
- package/src/modules/workspace/startup/steps/template-files.step.ts +4 -5
- package/src/modules/workspace/startup/steps/template-source.ts +21 -6
- package/src/modules/workspace/workspace-tmp.ts +4 -4
- package/src/modules/workspace/workspace.routes.ts +26 -26
- package/src/modules/workspace/workspace.service.ts +11 -11
- package/src/modules/workspace/workspace.tools.ts +90 -30
- package/src/shared/__tests__/canonical-file-identity.test.ts +5 -5
- package/src/shared/__tests__/http-errors.test.ts +4 -4
- package/src/shared/__tests__/read-capped.test.ts +68 -0
- package/src/shared/canonical-file-identity.ts +5 -5
- package/src/shared/domain-errors.ts +44 -44
- package/src/shared/git.contract.ts +1 -1
- package/src/shared/http-errors.ts +3 -3
- package/src/shared/kb-context.ts +130 -130
- package/src/shared/oauth-token-error.ts +46 -0
- package/src/shared/pending-proposals.ts +4 -4
- package/src/shared/read-capped.ts +40 -0
- package/src/tenancy/__tests__/tenant-host.test.ts +372 -372
- package/src/tenancy/__tests__/tenant-runtime.test.ts +221 -221
- package/src/tenancy/__tests__/tenant-secrets.test.ts +35 -35
- package/src/tenancy/index.ts +35 -35
- package/src/tenancy/static-tenant-source.ts +1 -1
- package/src/tenancy/tenant-host.ts +261 -261
- package/src/tenancy/tenant-runtime.ts +208 -208
- package/src/tenancy/tenant-source.contract.ts +59 -59
- package/agent-guide/finding-things.md +0 -7
- package/agent-guide/introduction.md +0 -27
- package/dist/modules/workflow/agent-tools/change-request-read-shape.d.ts.map +0 -1
- package/dist/modules/workflow/agent-tools/change-request-read-shape.js.map +0 -1
- package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +0 -1
- package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +0 -1
- package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +0 -1
- package/dist/modules/workflow/agent-tools/change-request-summary.js.map +0 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +0 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +0 -1
- package/dist/modules/workflow/change-request-hooks.d.ts.map +0 -1
- package/dist/modules/workflow/change-request-hooks.js.map +0 -1
- package/dist/modules/workflow/event-bus.d.ts.map +0 -1
- package/dist/modules/workflow/event-bus.js.map +0 -1
- package/dist/modules/workflow/events-delivery.d.ts.map +0 -1
- package/dist/modules/workflow/events-delivery.js.map +0 -1
- package/dist/modules/workflow/events.routes.d.ts.map +0 -1
- package/dist/modules/workflow/events.routes.js.map +0 -1
- package/dist/modules/workflow/file-lock.service.d.ts.map +0 -1
- package/dist/modules/workflow/file-lock.service.js.map +0 -1
- package/dist/modules/workflow/git/change-request-link.d.ts.map +0 -1
- package/dist/modules/workflow/git/change-request-link.js.map +0 -1
- package/dist/modules/workflow/git/git-version.d.ts.map +0 -1
- package/dist/modules/workflow/git/git-version.js.map +0 -1
- package/dist/modules/workflow/git/git.service.d.ts.map +0 -1
- package/dist/modules/workflow/git/git.service.js.map +0 -1
- package/dist/modules/workflow/git/merge-commit.d.ts.map +0 -1
- package/dist/modules/workflow/git/merge-commit.js.map +0 -1
- package/dist/modules/workflow/git/node-git-runner.d.ts.map +0 -1
- package/dist/modules/workflow/git/node-git-runner.js.map +0 -1
- package/dist/modules/workflow/git/pull-request.service.d.ts.map +0 -1
- package/dist/modules/workflow/git/pull-request.service.js.map +0 -1
- package/dist/modules/workflow/index.d.ts.map +0 -1
- package/dist/modules/workflow/index.js.map +0 -1
- package/dist/modules/workflow/pending-commits.service.d.ts.map +0 -1
- package/dist/modules/workflow/pending-commits.service.js.map +0 -1
- package/dist/modules/workflow/pending-commits.worker.d.ts.map +0 -1
- package/dist/modules/workflow/pending-commits.worker.js.map +0 -1
- package/dist/modules/workflow/read-on-branch.d.ts.map +0 -1
- package/dist/modules/workflow/read-on-branch.js.map +0 -1
- package/dist/modules/workflow/recovery-bot.d.ts.map +0 -1
- package/dist/modules/workflow/recovery-bot.js.map +0 -1
- package/dist/modules/workflow/review-workflow/approval-lock.d.ts.map +0 -1
- package/dist/modules/workflow/review-workflow/approval-lock.js.map +0 -1
- package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts.map +0 -1
- package/dist/modules/workflow/review-workflow/review-workflow.interface.js +0 -2
- package/dist/modules/workflow/review-workflow/review-workflow.interface.js.map +0 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +0 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +0 -1
- package/dist/modules/workflow/sanitize-error.d.ts.map +0 -1
- package/dist/modules/workflow/sanitize-error.js.map +0 -1
- package/dist/modules/workflow/workflow-hooks.d.ts.map +0 -1
- package/dist/modules/workflow/workflow-hooks.js.map +0 -1
- package/dist/modules/workflow/workflow.routes.d.ts.map +0 -1
- package/dist/modules/workflow/workflow.routes.js.map +0 -1
- package/dist/modules/workflow/workflow.service.d.ts.map +0 -1
- package/dist/modules/workflow/workflow.service.js.map +0 -1
- package/src/modules/mcp/__tests__/read-only-hint.e2e.test.ts +0 -213
- /package/dist/modules/{workflow → version-control}/agent-tools/change-request-read-shape.d.ts +0 -0
- /package/dist/modules/{workflow → version-control}/agent-tools/change-request-read-shape.js +0 -0
- /package/dist/modules/{workflow → version-control}/agent-tools/change-request-read.tools.d.ts +0 -0
- /package/dist/modules/{workflow → version-control}/agent-tools/change-request-summary.d.ts +0 -0
- /package/dist/modules/{workflow → version-control}/agent-tools/change-request-summary.js +0 -0
- /package/dist/modules/{workflow → version-control}/change-request-hooks.js +0 -0
- /package/dist/modules/{workflow/review-workflow → version-control/change-request-review}/approval-lock.d.ts +0 -0
- /package/dist/modules/{workflow/review-workflow → version-control/change-request-review}/approval-lock.js +0 -0
- /package/dist/modules/{workflow → version-control}/events-delivery.js +0 -0
- /package/dist/modules/{workflow → version-control}/events.routes.js +0 -0
- /package/dist/modules/{workflow → version-control}/git/change-request-link.d.ts +0 -0
- /package/dist/modules/{workflow → version-control}/git/change-request-link.js +0 -0
- /package/dist/modules/{workflow → version-control}/git/git-version.d.ts +0 -0
- /package/dist/modules/{workflow → version-control}/git/git-version.js +0 -0
- /package/dist/modules/{workflow → version-control}/git/merge-commit.d.ts +0 -0
- /package/dist/modules/{workflow → version-control}/git/merge-commit.js +0 -0
- /package/dist/modules/{workflow → version-control}/git/node-git-runner.d.ts +0 -0
- /package/dist/modules/{workflow → version-control}/recovery-bot.d.ts +0 -0
- /package/dist/modules/{workflow → version-control}/recovery-bot.js +0 -0
- /package/dist/modules/{workflow → version-control}/sanitize-error.d.ts +0 -0
- /package/dist/modules/{workflow → version-control}/sanitize-error.js +0 -0
- /package/src/modules/{workflow → version-control}/__tests__/change-request-hooks.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/__tests__/fake-file-lock-db.ts +0 -0
- /package/src/modules/{workflow → version-control}/__tests__/pending-commits.canonical.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/__tests__/pending-commits.enqueue-if-absent.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/__tests__/pending-commits.repository-replaced.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/__tests__/sanitize-error.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/agent-tools/__tests__/change-request-read-shape.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/agent-tools/change-request-read-shape.ts +0 -0
- /package/src/modules/{workflow → version-control}/agent-tools/change-request-summary.ts +0 -0
- /package/src/modules/{workflow/review-workflow → version-control/change-request-review}/approval-lock.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/__tests__/branchAuthor.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/__tests__/change-request-link.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/__tests__/git-version.failure.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/__tests__/git-version.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/__tests__/node-git-runner.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.getPrDetail.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.placeholder.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.viewer-can-cancel.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.viewer-can-delete.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.viewer-can-update.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/change-request-link.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/git-version.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/merge-commit.ts +0 -0
- /package/src/modules/{workflow → version-control}/recovery-bot.ts +0 -0
- /package/src/modules/{workflow → version-control}/sanitize-error.ts +0 -0
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
2
3
|
import { logger } from '../../shared/logging.js';
|
|
3
4
|
|
|
4
5
|
const log = logger('vault');
|
|
5
6
|
import { and, desc, eq, inArray, isNull, like, or } from 'drizzle-orm';
|
|
6
7
|
import type { Database } from '../database/index.js';
|
|
7
|
-
import { secrets } from '../database/index.js';
|
|
8
|
+
import { pgErrorCode, secrets } from '../database/index.js';
|
|
8
9
|
import { TokenCrypto } from '../../shared/token-crypto.js';
|
|
9
10
|
import { assertSafeFetchUrl } from '../../shared/ssrf.js';
|
|
11
|
+
import { readCapped } from '../../shared/read-capped.js';
|
|
12
|
+
import { standardTokenErrorCode } from '../../shared/oauth-token-error.js';
|
|
10
13
|
import {
|
|
11
14
|
type ISecretsVaultService,
|
|
12
15
|
type SecretSummary,
|
|
@@ -16,14 +19,21 @@ import {
|
|
|
16
19
|
type PutSharedStaticSecretInput,
|
|
17
20
|
type CreateOAuthSecretInput,
|
|
18
21
|
type OAuthProviderConfig,
|
|
22
|
+
type DeclaredOAuthProvider,
|
|
19
23
|
type VariableScopeResolver,
|
|
20
24
|
type NamespaceSecretCount,
|
|
21
25
|
isKeyInNamespace,
|
|
22
26
|
type ForcedRefreshOutcome,
|
|
27
|
+
type ManagedSignInRef,
|
|
28
|
+
type ManagedTokenRefresher,
|
|
29
|
+
type ManagedTokenRevoker,
|
|
30
|
+
type DeliveredTokenSet,
|
|
31
|
+
type ManagedRowsWriter,
|
|
23
32
|
InvalidSecretError,
|
|
24
33
|
SecretNotFoundError,
|
|
25
34
|
SecretOAuthError,
|
|
26
35
|
} from './secrets-vault.contract.js';
|
|
36
|
+
import { timingSafeStringEqual } from '../auth/index.js';
|
|
27
37
|
|
|
28
38
|
const MAX_KEY_LEN = 200;
|
|
29
39
|
const MAX_LABEL_LEN = 200;
|
|
@@ -32,6 +42,104 @@ const MAX_VALUE_LEN = 100_000;
|
|
|
32
42
|
const REFRESH_SKEW_MS = 60_000;
|
|
33
43
|
/** Upper bound on a token-endpoint round-trip, so a hung provider can't block callers. */
|
|
34
44
|
const TOKEN_REQUEST_TIMEOUT_MS = 10_000;
|
|
45
|
+
/** Upper bound on telling a provider a managed sign-in is over, so a deletion never waits on it. */
|
|
46
|
+
const REVOKE_TIMEOUT_MS = 10_000;
|
|
47
|
+
/** Upper bound on a broker renewing a managed token: past it the stale token is served and the next call asks again. */
|
|
48
|
+
const REFRESH_TIMEOUT_MS = 15_000;
|
|
49
|
+
/** How many times a guarded replacement re-reads a row that changed under its revoke before giving up. */
|
|
50
|
+
const REPLACE_ATTEMPTS = 3;
|
|
51
|
+
/** How many times a handoff re-reads a row a refresh rotated under it (the nonce rides along) before refusing. */
|
|
52
|
+
const HANDOFF_ATTEMPTS = 3;
|
|
53
|
+
|
|
54
|
+
/** `work`, or a rejection once `ms` have passed: a handler that never settles must not hold the vault. */
|
|
55
|
+
async function bounded<T>(work: Promise<T>, ms: number, what: string): Promise<T> {
|
|
56
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
57
|
+
try {
|
|
58
|
+
return await Promise.race([
|
|
59
|
+
work,
|
|
60
|
+
new Promise<never>((_, reject) => {
|
|
61
|
+
timer = setTimeout(() => reject(new Error(`${what} did not settle within ${ms}ms`)), ms);
|
|
62
|
+
}),
|
|
63
|
+
]);
|
|
64
|
+
} finally {
|
|
65
|
+
if (timer) clearTimeout(timer);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** How often a managed sign-in's opening is retried against a row that keeps changing under it. */
|
|
69
|
+
const BEGIN_MANAGED_ATTEMPTS = 3;
|
|
70
|
+
/** How much of a refusing token endpoint's body is read for its error code: an OAuth error object, never a whole error page. */
|
|
71
|
+
const TOKEN_ERROR_BODY_MAX_BYTES = 2048;
|
|
72
|
+
// Hexis's own sentences for a refused token request: what to change, in our
|
|
73
|
+
// words, never the provider's.
|
|
74
|
+
const HINT_CLIENT =
|
|
75
|
+
'The client id or client secret this sign-in uses is not accepted by the provider. The tool owner should check the declared client id and set the client secret again.';
|
|
76
|
+
const HINT_GRANT =
|
|
77
|
+
'The provider refused the grant: for a sign-in, a redirect URL mismatch or an expired or reused authorization code; for a refresh, a refresh token that has expired or been revoked. Sign in again.';
|
|
78
|
+
const HINT_SCOPE =
|
|
79
|
+
"The provider refused the scopes this sign-in asks for. The tool owner should check the declared scopes against the provider's app.";
|
|
80
|
+
const HINT_UNAUTHORIZED =
|
|
81
|
+
"The provider does not allow this client to use this grant. The tool owner should check the provider's app settings.";
|
|
82
|
+
const HINT_REQUEST =
|
|
83
|
+
'The provider found the token request malformed. The tool owner should check the declared endpoints and parameters.';
|
|
84
|
+
const HINT_PROVIDER = 'The provider could not serve the token request. Try again later.';
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Hexis's sentence for each error code a refusing token endpoint may answer:
|
|
88
|
+
* the standard ones (`shared/oauth-token-error.ts` is the one list of those,
|
|
89
|
+
* read here and by the OIDC configuration check) in `error`, and the ones
|
|
90
|
+
* HubSpot's token endpoint answers in its `status` field. Only a code from
|
|
91
|
+
* those lists is ever taken, exactly, so no other string a provider puts in a
|
|
92
|
+
* field — a secret, a secret with a suffix, free text — can reach a message.
|
|
93
|
+
* Nothing else of the body is ever taken: no description, no message, no first
|
|
94
|
+
* line. That is why a message built here needs no scrubbing, cutting or
|
|
95
|
+
* decoding, and why every spelling a secret could reach a log in (escaped,
|
|
96
|
+
* encoded, cut) stays inexpressible. A standard code without a sentence here
|
|
97
|
+
* (`access_denied`) is named and left unexplained.
|
|
98
|
+
*/
|
|
99
|
+
const TOKEN_ERROR_HINTS: ReadonlyMap<string, string> = new Map([
|
|
100
|
+
['invalid_request', HINT_REQUEST],
|
|
101
|
+
['invalid_client', HINT_CLIENT],
|
|
102
|
+
['invalid_grant', HINT_GRANT],
|
|
103
|
+
['unauthorized_client', HINT_UNAUTHORIZED],
|
|
104
|
+
['unsupported_grant_type', HINT_UNAUTHORIZED],
|
|
105
|
+
['invalid_scope', HINT_SCOPE],
|
|
106
|
+
['server_error', HINT_PROVIDER],
|
|
107
|
+
['temporarily_unavailable', HINT_PROVIDER],
|
|
108
|
+
['BAD_CLIENT_ID', HINT_CLIENT],
|
|
109
|
+
['BAD_CLIENT_SECRET', HINT_CLIENT],
|
|
110
|
+
['BAD_AUTH_CODE', HINT_GRANT],
|
|
111
|
+
['BAD_REDIRECT_URI', HINT_GRANT],
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
/** The codes HubSpot's token endpoint answers in `status` instead of a standard `error`. */
|
|
115
|
+
const HUBSPOT_STATUS_CODES: ReadonlySet<string> = new Set(['BAD_CLIENT_ID', 'BAD_CLIENT_SECRET', 'BAD_AUTH_CODE', 'BAD_REDIRECT_URI']);
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The known error code a refusing token endpoint answered, or null: a standard
|
|
119
|
+
* `error` (RFC 6749 §5.2), else HubSpot's own `status`, when it is exactly one
|
|
120
|
+
* of the codes known. Without even the code, a bare "HTTP 400" cannot tell a
|
|
121
|
+
* client the provider does not accept from a refused grant.
|
|
122
|
+
*/
|
|
123
|
+
export function tokenEndpointErrorCode(bodyText: string): string | null {
|
|
124
|
+
const standard = standardTokenErrorCode(bodyText);
|
|
125
|
+
if (standard !== null) return standard;
|
|
126
|
+
let json: unknown;
|
|
127
|
+
try {
|
|
128
|
+
json = JSON.parse(bodyText);
|
|
129
|
+
} catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
if (!json || typeof json !== 'object' || Array.isArray(json)) return null;
|
|
133
|
+
const { status } = json as Record<string, unknown>;
|
|
134
|
+
return typeof status === 'string' && HUBSPOT_STATUS_CODES.has(status) ? status : null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** The refusal in one message: the status, the known code when there is one, and Hexis's sentence for it. */
|
|
138
|
+
export function describeTokenEndpointRefusal(status: number, code: string | null): string {
|
|
139
|
+
const base = `Token endpoint returned HTTP ${status}`;
|
|
140
|
+
const hint = code === null ? undefined : TOKEN_ERROR_HINTS.get(code);
|
|
141
|
+
return hint ? `${base} (${code}). ${hint}` : base;
|
|
142
|
+
}
|
|
35
143
|
|
|
36
144
|
/**
|
|
37
145
|
* How a row is stored, or `null` when there is no row — the column is a plain
|
|
@@ -78,6 +186,14 @@ interface OAuthBlob {
|
|
|
78
186
|
* like the verifier: consumed by the exchange it was minted for.
|
|
79
187
|
*/
|
|
80
188
|
pendingScopes?: string;
|
|
189
|
+
/**
|
|
190
|
+
* A managed sign-in in progress: the one-time nonce the broker's handoff
|
|
191
|
+
* must echo. Stored encrypted like the verifier, and for the same reason —
|
|
192
|
+
* it binds the handoff to the sign-in this row opened, so a handoff that
|
|
193
|
+
* is replayed or forged lands nowhere. Cleared by the handoff it was
|
|
194
|
+
* minted for.
|
|
195
|
+
*/
|
|
196
|
+
pendingNonce?: string;
|
|
81
197
|
}
|
|
82
198
|
|
|
83
199
|
/**
|
|
@@ -94,6 +210,20 @@ interface OAuthBlob {
|
|
|
94
210
|
*/
|
|
95
211
|
export class DbSecretsVaultService implements ISecretsVaultService {
|
|
96
212
|
private cryptoInstance: TokenCrypto | null = null;
|
|
213
|
+
/**
|
|
214
|
+
* How a MANAGED sign-in's tokens are renewed and revoked — through the
|
|
215
|
+
* broker and the provider respectively. Setter-injected: both depend on
|
|
216
|
+
* the connector catalog, which is built after this service. Without them a
|
|
217
|
+
* managed token that expires is served stale (a transient, retried on the
|
|
218
|
+
* next call) and a disconnect deletes without revoking.
|
|
219
|
+
*/
|
|
220
|
+
private managedRefresher?: ManagedTokenRefresher;
|
|
221
|
+
private managedRevoker?: ManagedTokenRevoker;
|
|
222
|
+
|
|
223
|
+
setManagedTokenHandlers(handlers: { refresher?: ManagedTokenRefresher; revoker?: ManagedTokenRevoker }): void {
|
|
224
|
+
this.managedRefresher = handlers.refresher;
|
|
225
|
+
this.managedRevoker = handlers.revoker;
|
|
226
|
+
}
|
|
97
227
|
|
|
98
228
|
constructor(
|
|
99
229
|
private readonly db: Database,
|
|
@@ -169,15 +299,20 @@ export class DbSecretsVaultService implements ISecretsVaultService {
|
|
|
169
299
|
const label = this.normaliseLabel(input.label);
|
|
170
300
|
const valueEncrypted = this.crypto().encrypt(input.value);
|
|
171
301
|
|
|
302
|
+
// The caller's row may hold a managed sign-in: its tokens are told to the
|
|
303
|
+
// provider before this value replaces them, as on every replacement.
|
|
172
304
|
// Upsert on (user_id, key): re-saving a key replaces its value in place.
|
|
173
|
-
const
|
|
174
|
-
.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
305
|
+
const row = await this.revokedReplacement(and(eq(secrets.userId, userId), eq(secrets.key, key)), (guard) =>
|
|
306
|
+
this.db
|
|
307
|
+
.insert(secrets)
|
|
308
|
+
.values({ userId, key, kind: 'static', label, valueEncrypted, oauthMeta: null })
|
|
309
|
+
.onConflictDoUpdate({
|
|
310
|
+
target: [secrets.userId, secrets.key],
|
|
311
|
+
set: { kind: 'static', label, valueEncrypted, oauthMeta: null, updatedAt: new Date() },
|
|
312
|
+
...(guard ? { setWhere: guard } : {}),
|
|
313
|
+
})
|
|
314
|
+
.returning(),
|
|
315
|
+
);
|
|
181
316
|
this.notifyMutation(userId);
|
|
182
317
|
return this.toSummary(row);
|
|
183
318
|
}
|
|
@@ -192,26 +327,28 @@ export class DbSecretsVaultService implements ISecretsVaultService {
|
|
|
192
327
|
const valueEncrypted = this.crypto().encrypt(JSON.stringify(blob));
|
|
193
328
|
const oauthMeta = this.publicProviderMeta(provider);
|
|
194
329
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
330
|
+
// A managed sign-in under this key loses its tokens here: the provider is
|
|
331
|
+
// told first, as on every replacement.
|
|
332
|
+
const row = await this.revokedReplacement(and(eq(secrets.userId, userId), eq(secrets.key, key)), (guard) =>
|
|
333
|
+
this.db
|
|
334
|
+
.insert(secrets)
|
|
335
|
+
.values({ userId, key, kind: 'oauth', label, valueEncrypted, oauthMeta })
|
|
336
|
+
.onConflictDoUpdate({
|
|
337
|
+
target: [secrets.userId, secrets.key],
|
|
338
|
+
// Replacing an oauth secret's provider config drops any existing tokens
|
|
339
|
+
// (they belonged to the old client) — the user must re-authorize.
|
|
340
|
+
set: { kind: 'oauth', label, valueEncrypted, oauthMeta, updatedAt: new Date() },
|
|
341
|
+
...(guard ? { setWhere: guard } : {}),
|
|
342
|
+
})
|
|
343
|
+
.returning(),
|
|
344
|
+
);
|
|
205
345
|
this.notifyMutation(userId);
|
|
206
346
|
return this.toSummary(row);
|
|
207
347
|
}
|
|
208
348
|
|
|
209
349
|
async remove(userId: string, id: string): Promise<void> {
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
.where(and(eq(secrets.id, id), eq(secrets.userId, userId)))
|
|
213
|
-
.returning({ id: secrets.id });
|
|
214
|
-
if (res.length === 0) throw new SecretNotFoundError(id);
|
|
350
|
+
const gone = await this.revokeThenDelete(and(eq(secrets.id, id), eq(secrets.userId, userId)));
|
|
351
|
+
if (gone.length === 0) throw new SecretNotFoundError(id);
|
|
215
352
|
this.notifyMutation(userId);
|
|
216
353
|
}
|
|
217
354
|
|
|
@@ -226,37 +363,199 @@ export class DbSecretsVaultService implements ISecretsVaultService {
|
|
|
226
363
|
const label = this.normaliseLabel(input.label);
|
|
227
364
|
const valueEncrypted = this.crypto().encrypt(input.value);
|
|
228
365
|
|
|
366
|
+
// A shared row that held a managed workspace connection is told to its
|
|
367
|
+
// provider before this value replaces it; the tokens would otherwise go
|
|
368
|
+
// unrevoked with the row's kind.
|
|
229
369
|
// Upsert the ONE shared row for this key (user_id NULL). The conflict target
|
|
230
370
|
// is the partial unique index `secrets_shared_key_unq` — hence `targetWhere`.
|
|
231
|
-
const
|
|
232
|
-
.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
371
|
+
const row = await this.revokedReplacement(and(isNull(secrets.userId), eq(secrets.key, key)), (guard) =>
|
|
372
|
+
this.db
|
|
373
|
+
.insert(secrets)
|
|
374
|
+
.values({ userId: null, key, kind: 'static', label, valueEncrypted, oauthMeta: null })
|
|
375
|
+
.onConflictDoUpdate({
|
|
376
|
+
target: secrets.key,
|
|
377
|
+
targetWhere: isNull(secrets.userId),
|
|
378
|
+
set: { kind: 'static', label, valueEncrypted, oauthMeta: null, updatedAt: new Date() },
|
|
379
|
+
...(guard ? { setWhere: guard } : {}),
|
|
380
|
+
})
|
|
381
|
+
.returning(),
|
|
382
|
+
);
|
|
240
383
|
this.notifyMutation(null);
|
|
241
384
|
return this.toSummary(row);
|
|
242
385
|
}
|
|
243
386
|
|
|
244
387
|
async removeShared(key: string): Promise<void> {
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
.where(and(isNull(secrets.userId), eq(secrets.key, key)))
|
|
248
|
-
.returning({ id: secrets.id });
|
|
249
|
-
if (res.length === 0) throw new SecretNotFoundError(key);
|
|
388
|
+
const gone = await this.revokeThenDelete(and(isNull(secrets.userId), eq(secrets.key, key)));
|
|
389
|
+
if (gone.length === 0) throw new SecretNotFoundError(key);
|
|
250
390
|
this.notifyMutation(null);
|
|
251
391
|
}
|
|
252
392
|
|
|
253
393
|
async removeUserByKey(userId: string, key: string): Promise<void> {
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
394
|
+
const gone = await this.revokeThenDelete(and(eq(secrets.userId, userId), eq(secrets.key, key)));
|
|
395
|
+
if (gone.length === 0) throw new SecretNotFoundError(key);
|
|
396
|
+
this.notifyMutation(userId);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Delete the rows `where` names, each told to its provider first when it
|
|
401
|
+
* is a managed sign-in with tokens (see {@link revokeRow}). The delete is
|
|
402
|
+
* guarded on the ciphertext the revoke read: a handoff or a refresh landing
|
|
403
|
+
* in between has replaced the tokens, and deleting THOSE unrevoked is the
|
|
404
|
+
* leak the revoke exists to prevent. A miss re-reads the row and goes
|
|
405
|
+
* again, bounded; what could not be settled in those passes is left
|
|
406
|
+
* standing rather than deleted blind.
|
|
407
|
+
*/
|
|
408
|
+
private async revokeThenDelete(where: ReturnType<typeof and>): Promise<{ id: string; userId: string | null; kind: string }[]> {
|
|
409
|
+
// No revoker, nothing to read first: one plain delete, as every removal
|
|
410
|
+
// was before managed sign-ins, and no miss to re-read for.
|
|
411
|
+
if (!this.managedRevoker) {
|
|
412
|
+
return this.db.delete(secrets).where(where).returning({ id: secrets.id, userId: secrets.userId, kind: secrets.kind });
|
|
413
|
+
}
|
|
414
|
+
const gone: { id: string; userId: string | null; kind: string }[] = [];
|
|
415
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
416
|
+
const rows = await this.db.select().from(secrets).where(where);
|
|
417
|
+
if (rows.length === 0) break;
|
|
418
|
+
// Every row's revocation at once, so a bulk delete (a namespace, a
|
|
419
|
+
// person's connections) waits one revocation's bound, not one per row.
|
|
420
|
+
await Promise.all(rows.map((row) => this.revokeRow(row)));
|
|
421
|
+
let missed = false;
|
|
422
|
+
for (const row of rows) {
|
|
423
|
+
const deleted = await this.db
|
|
424
|
+
.delete(secrets)
|
|
425
|
+
.where(and(eq(secrets.id, row.id), eq(secrets.valueEncrypted, row.valueEncrypted)))
|
|
426
|
+
.returning({ id: secrets.id, userId: secrets.userId, kind: secrets.kind });
|
|
427
|
+
if (deleted.length === 0) missed = true;
|
|
428
|
+
else gone.push(...deleted);
|
|
429
|
+
}
|
|
430
|
+
if (!missed) break;
|
|
431
|
+
}
|
|
432
|
+
return gone;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async removeManagedRowsOfUser(userId: string, within?: ManagedRowsWriter): Promise<number> {
|
|
436
|
+
if (within) return this.removeManagedRowsLocked(userId, within);
|
|
437
|
+
// Every oauth row of the user whose meta names a managed app. Read
|
|
438
|
+
// first (the managed mark lives in JSON the WHERE cannot see), revoked,
|
|
439
|
+
// then deleted by id; the per-user filter on the delete is what keeps a
|
|
440
|
+
// row read for this user from ever deleting another's.
|
|
441
|
+
const rows = await this.db
|
|
442
|
+
.select()
|
|
443
|
+
.from(secrets)
|
|
444
|
+
.where(and(eq(secrets.userId, userId), eq(secrets.kind, 'oauth')));
|
|
445
|
+
const managed = rows.filter((r) => this.managedRefOf(r.oauthMeta) !== null);
|
|
446
|
+
if (managed.length === 0) return 0;
|
|
447
|
+
const deleted = await this.revokeThenDelete(and(eq(secrets.userId, userId), inArray(secrets.id, managed.map((r) => r.id))));
|
|
448
|
+
if (deleted.length > 0) this.notifyMutation(userId);
|
|
449
|
+
// A row the guarded delete could not settle (its tokens kept changing
|
|
450
|
+
// under the revoke) is still here, unrevoked. The caller is told rather
|
|
451
|
+
// than left to believe the person holds nothing: an erasure refuses and
|
|
452
|
+
// is retried, a switch-off logs it and the row is refused on use.
|
|
453
|
+
if (deleted.length < managed.length) {
|
|
454
|
+
throw new SecretOAuthError(
|
|
455
|
+
`${managed.length - deleted.length} managed sign-in(s) kept changing while being revoked and were not removed; try again`,
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
return deleted.length;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* The same removal inside a caller's transaction (an account erasure, just
|
|
463
|
+
* before the user row goes): the rows are LOCKED for the transaction, so a
|
|
464
|
+
* handoff or a refresh arriving now waits and then finds no row, which is
|
|
465
|
+
* what makes the revoke final. No guard is needed under the lock; the
|
|
466
|
+
* revoke stays bounded (see {@link revokeRow}), so the transaction never
|
|
467
|
+
* waits on a provider longer than that.
|
|
468
|
+
*/
|
|
469
|
+
private async removeManagedRowsLocked(userId: string, tx: ManagedRowsWriter): Promise<number> {
|
|
470
|
+
const rows = await tx
|
|
471
|
+
.select()
|
|
472
|
+
.from(secrets)
|
|
473
|
+
.where(and(eq(secrets.userId, userId), eq(secrets.kind, 'oauth')))
|
|
474
|
+
.for('update');
|
|
475
|
+
const managed = rows.filter((r) => this.managedRefOf(r.oauthMeta) !== null);
|
|
476
|
+
if (managed.length === 0) return 0;
|
|
477
|
+
await Promise.all(managed.map((row) => this.revokeRow(row)));
|
|
478
|
+
await tx.delete(secrets).where(
|
|
479
|
+
and(
|
|
480
|
+
eq(secrets.userId, userId),
|
|
481
|
+
inArray(
|
|
482
|
+
secrets.id,
|
|
483
|
+
managed.map((r) => r.id),
|
|
484
|
+
),
|
|
485
|
+
),
|
|
486
|
+
);
|
|
259
487
|
this.notifyMutation(userId);
|
|
488
|
+
return managed.length;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Replace what `where` names with `write`, a managed sign-in's tokens told
|
|
493
|
+
* to their provider first (see {@link revokeRow}; best effort, never holding
|
|
494
|
+
* the user's decision). The write is guarded on the ciphertext the revoke
|
|
495
|
+
* read (`setWhere`): a refresh or a handoff landing in between has rotated
|
|
496
|
+
* the tokens, and writing over THOSE unrevoked is the leak the revoke exists
|
|
497
|
+
* to prevent — as the guarded delete of {@link revokeThenDelete}. A miss
|
|
498
|
+
* re-reads and goes again, bounded; a row that kept changing is left as it
|
|
499
|
+
* is and the caller is told. With no revoker wired there is nothing to read
|
|
500
|
+
* first: one plain write, as every replacement was before managed sign-ins.
|
|
501
|
+
*/
|
|
502
|
+
private async revokedReplacement<T>(
|
|
503
|
+
where: ReturnType<typeof and>,
|
|
504
|
+
write: (guard?: ReturnType<typeof eq>) => Promise<T[]>,
|
|
505
|
+
): Promise<T> {
|
|
506
|
+
if (!this.managedRevoker) return (await write())[0] as T;
|
|
507
|
+
for (let attempt = 0; attempt < REPLACE_ATTEMPTS; attempt++) {
|
|
508
|
+
const rows = await this.db.select().from(secrets).where(where);
|
|
509
|
+
await Promise.all(rows.map((row) => this.revokeRow(row)));
|
|
510
|
+
// The row as it was read, or no row at all: a conflict with anything
|
|
511
|
+
// else (a row written meanwhile) is a miss, and the next pass reads it.
|
|
512
|
+
const guard =
|
|
513
|
+
rows.length > 0
|
|
514
|
+
? inArray(
|
|
515
|
+
secrets.valueEncrypted,
|
|
516
|
+
rows.map((row) => row.valueEncrypted),
|
|
517
|
+
)
|
|
518
|
+
: eq(secrets.valueEncrypted, '');
|
|
519
|
+
const written = await write(guard);
|
|
520
|
+
if (written.length > 0) return written[0] as T;
|
|
521
|
+
}
|
|
522
|
+
throw new SecretOAuthError('the sign-in kept changing while being replaced; try again');
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
private async revokeRow(row: typeof secrets.$inferSelect): Promise<void> {
|
|
526
|
+
if (!this.managedRevoker || row.kind !== 'oauth') return;
|
|
527
|
+
const ref = this.managedRefOf(row.oauthMeta);
|
|
528
|
+
if (!ref) return;
|
|
529
|
+
const tokens = this.readTokensSafe(row.valueEncrypted);
|
|
530
|
+
if (!tokens?.access_token) return;
|
|
531
|
+
// Bounded: the delete that follows is the user's decision, and a revoker
|
|
532
|
+
// that never settles must not hold it.
|
|
533
|
+
try {
|
|
534
|
+
await bounded(
|
|
535
|
+
this.managedRevoker.revoke(ref, { access_token: tokens.access_token, refresh_token: tokens.refresh_token }),
|
|
536
|
+
REVOKE_TIMEOUT_MS,
|
|
537
|
+
'revocation',
|
|
538
|
+
);
|
|
539
|
+
} catch (err) {
|
|
540
|
+
// The key and the connector name the sign-in. The error itself stays
|
|
541
|
+
// out: a revoker's message is its own, and this line is the one place a
|
|
542
|
+
// token could otherwise reach a log from this path.
|
|
543
|
+
log.warn('revoking a managed sign-in with its provider failed; deleting it here anyway', {
|
|
544
|
+
key: row.key,
|
|
545
|
+
connector: ref.connectorId,
|
|
546
|
+
failure: err instanceof Error ? err.name : typeof err,
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/** The managed reference a row's public meta carries, or null for an ordinary sign-in. */
|
|
552
|
+
private managedRefOf(raw: unknown): ManagedSignInRef | null {
|
|
553
|
+
const meta = (raw ?? {}) as Record<string, unknown>;
|
|
554
|
+
const m = meta.managed as Record<string, unknown> | undefined;
|
|
555
|
+
if (!m || typeof m !== 'object') return null;
|
|
556
|
+
const connectorId = typeof m.connectorId === 'string' ? m.connectorId : '';
|
|
557
|
+
const scopeSet = typeof m.scopeSet === 'string' ? m.scopeSet : '';
|
|
558
|
+
return connectorId && scopeSet ? { connectorId, scopeSet } : null;
|
|
260
559
|
}
|
|
261
560
|
|
|
262
561
|
async countNamespace(prefix: string): Promise<NamespaceSecretCount> {
|
|
@@ -279,10 +578,10 @@ export class DbSecretsVaultService implements ISecretsVaultService {
|
|
|
279
578
|
for (let pass = 0; pass < 5; pass++) {
|
|
280
579
|
const rows = await this.namespaceRows(prefix);
|
|
281
580
|
if (rows.length === 0) break;
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
581
|
+
// A managed sign-in under the namespace is told to its provider
|
|
582
|
+
// before the delete, for the same reason the per-row removals do —
|
|
583
|
+
// and guarded the same way, when a revoker is there to need it.
|
|
584
|
+
const deleted = await this.revokeThenDelete(inArray(secrets.id, rows.map((r) => r.id)));
|
|
286
585
|
gone.push(...deleted);
|
|
287
586
|
}
|
|
288
587
|
} finally {
|
|
@@ -332,6 +631,7 @@ export class DbSecretsVaultService implements ISecretsVaultService {
|
|
|
332
631
|
userId: secrets.userId,
|
|
333
632
|
kind: secrets.kind,
|
|
334
633
|
valueEncrypted: secrets.valueEncrypted,
|
|
634
|
+
oauthMeta: secrets.oauthMeta,
|
|
335
635
|
})
|
|
336
636
|
.from(secrets)
|
|
337
637
|
.where(and(inArray(secrets.key, keys), or(isNull(secrets.userId), eq(secrets.userId, userId))));
|
|
@@ -340,6 +640,15 @@ export class DbSecretsVaultService implements ISecretsVaultService {
|
|
|
340
640
|
const adminRow = rows.find((r) => r.key === key && r.userId === null);
|
|
341
641
|
// Decrypt+parse the oauth row's token set once, then derive both fields from it.
|
|
342
642
|
const tokens = userRow?.kind === 'oauth' ? this.readTokensSafe(userRow.valueEncrypted) : undefined;
|
|
643
|
+
// The shared row holds tokens only for a managed workspace connection;
|
|
644
|
+
// an ordinary sign-in's shared row is the client secret and reads as
|
|
645
|
+
// "no tokens" here, which is the truthful answer for it.
|
|
646
|
+
const adminTokens = adminRow?.kind === 'oauth' ? this.readTokensSafe(adminRow.valueEncrypted) : undefined;
|
|
647
|
+
const adminMeta = (adminRow?.oauthMeta ?? null) as Record<string, unknown> | null;
|
|
648
|
+
const accountOf = (meta: unknown): string | undefined => {
|
|
649
|
+
const a = (meta as Record<string, unknown> | null)?.account;
|
|
650
|
+
return typeof a === 'string' && a ? a : undefined;
|
|
651
|
+
};
|
|
343
652
|
return {
|
|
344
653
|
key,
|
|
345
654
|
adminConfigured: !!adminRow,
|
|
@@ -353,10 +662,157 @@ export class DbSecretsVaultService implements ISecretsVaultService {
|
|
|
353
662
|
// Only meaningful for an oauth row: the scopes the caller's token was granted,
|
|
354
663
|
// so a coverage check can tell whether it still covers the tool's live scopes.
|
|
355
664
|
grantedScopes: userRow?.kind === 'oauth' ? tokens?.scope : undefined,
|
|
665
|
+
adminAuthorized: adminRow?.kind === 'oauth' ? Boolean(adminTokens?.access_token) : undefined,
|
|
666
|
+
adminGrantedScopes: adminRow?.kind === 'oauth' ? adminTokens?.scope : undefined,
|
|
667
|
+
userAccount: userRow?.kind === 'oauth' ? accountOf(userRow.oauthMeta) : undefined,
|
|
668
|
+
adminAccount: adminRow?.kind === 'oauth' ? accountOf(adminMeta) : undefined,
|
|
669
|
+
// An owner's own app on the shared row: a client id without the managed mark.
|
|
670
|
+
adminBringsOwnApp:
|
|
671
|
+
adminRow?.kind === 'oauth'
|
|
672
|
+
? this.managedRefOf(adminMeta) === null && typeof adminMeta?.clientId === 'string' && adminMeta.clientId !== ''
|
|
673
|
+
: undefined,
|
|
356
674
|
};
|
|
357
675
|
});
|
|
358
676
|
}
|
|
359
677
|
|
|
678
|
+
async beginManagedConnection(input: {
|
|
679
|
+
userId: string | null;
|
|
680
|
+
key: string;
|
|
681
|
+
provider: OAuthProviderConfig;
|
|
682
|
+
nonce: string;
|
|
683
|
+
}): Promise<void> {
|
|
684
|
+
const key = this.requireKey(input.key);
|
|
685
|
+
const userId = input.userId === null ? null : this.requireUserId(input.userId);
|
|
686
|
+
const ref = input.provider.managed;
|
|
687
|
+
if (!ref) throw new InvalidSecretError('a managed sign-in needs its connector reference');
|
|
688
|
+
if (typeof input.nonce !== 'string' || input.nonce.length < 16) throw new InvalidSecretError('nonce is required');
|
|
689
|
+
const oauthMeta = this.publicProviderMeta(this.requireProvider(input.provider, { managed: true }));
|
|
690
|
+
const where = userId === null ? and(isNull(secrets.userId), eq(secrets.key, key)) : and(eq(secrets.userId, userId), eq(secrets.key, key));
|
|
691
|
+
// Read-modify-write on a LIVE credential: a refresh running concurrently
|
|
692
|
+
// may rotate the tokens between the read and the write, and writing our
|
|
693
|
+
// copy over them would leave the row holding a dead refresh token. So the
|
|
694
|
+
// update is guarded on the ciphertext read, and a miss re-reads and
|
|
695
|
+
// rebuilds from the fresh row; bounded, so a row that keeps changing
|
|
696
|
+
// fails loudly rather than forever. The same rule `beginOAuth` follows.
|
|
697
|
+
for (let attempt = 0; attempt < BEGIN_MANAGED_ATTEMPTS; attempt++) {
|
|
698
|
+
const [existing] = await this.db.select().from(secrets).where(where).limit(1);
|
|
699
|
+
// Keep what the SAME connection already holds (its tokens, so an
|
|
700
|
+
// abandoned reconnect costs nothing). The same connection means the same
|
|
701
|
+
// connector and scope set: a row of another connector's, or of another
|
|
702
|
+
// set's, holds a token for something else, and a declaration that
|
|
703
|
+
// changed must not hand it to the new one. Start clean over anything
|
|
704
|
+
// else too — a static value or an owner-app sign-in under this key.
|
|
705
|
+
const storedRef = existing?.kind === 'oauth' ? this.managedRefOf(existing.oauthMeta) : null;
|
|
706
|
+
const sameConnection =
|
|
707
|
+
storedRef !== null && storedRef.connectorId === ref.connectorId && storedRef.scopeSet === ref.scopeSet;
|
|
708
|
+
let kept: OAuthBlob = {};
|
|
709
|
+
if (existing && sameConnection) {
|
|
710
|
+
try {
|
|
711
|
+
kept = this.readBlob(existing.valueEncrypted);
|
|
712
|
+
} catch {
|
|
713
|
+
kept = {};
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
const next: OAuthBlob = { tokens: kept.tokens, pendingNonce: input.nonce };
|
|
717
|
+
const valueEncrypted = this.crypto().encrypt(JSON.stringify(next));
|
|
718
|
+
const account = sameConnection ? (existing!.oauthMeta as Record<string, unknown> | null)?.account : undefined;
|
|
719
|
+
const meta = typeof account === 'string' && account ? { ...oauthMeta, account } : oauthMeta;
|
|
720
|
+
if (!existing) {
|
|
721
|
+
try {
|
|
722
|
+
await this.db.insert(secrets).values({ userId, key, kind: 'oauth', label: null, valueEncrypted, oauthMeta: meta });
|
|
723
|
+
return;
|
|
724
|
+
} catch (err) {
|
|
725
|
+
// Another start of the same sign-in inserted the row first: the
|
|
726
|
+
// next pass finds it and updates it, as it would any existing row.
|
|
727
|
+
if (pgErrorCode(err) === '23505') continue; // unique_violation
|
|
728
|
+
throw err;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
// A row that held ANOTHER connection's tokens is about to lose them:
|
|
732
|
+
// the provider is told first, as on every other replacement.
|
|
733
|
+
if (!sameConnection && storedRef) await this.revokeRow(existing);
|
|
734
|
+
const written = await this.db
|
|
735
|
+
.update(secrets)
|
|
736
|
+
.set({ kind: 'oauth', valueEncrypted, oauthMeta: meta, updatedAt: new Date() })
|
|
737
|
+
.where(and(eq(secrets.id, existing.id), eq(secrets.valueEncrypted, existing.valueEncrypted)))
|
|
738
|
+
.returning({ id: secrets.id });
|
|
739
|
+
if (Array.isArray(written) && written.length === 0) continue;
|
|
740
|
+
// A row that held something ELSE (a value, another connection's
|
|
741
|
+
// tokens, an owner's app) no longer does: caches keyed on it are told.
|
|
742
|
+
// The same connection's tokens carried over unchanged tell nobody.
|
|
743
|
+
if (!kept.tokens) this.notifyMutation(userId);
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
throw new SecretOAuthError('The sign-in could not be opened: its stored credential kept changing. Try again.');
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
async completeManagedConnection(input: {
|
|
750
|
+
userId: string | null;
|
|
751
|
+
key: string;
|
|
752
|
+
nonce: string;
|
|
753
|
+
tokens: DeliveredTokenSet;
|
|
754
|
+
account?: string;
|
|
755
|
+
}): Promise<void> {
|
|
756
|
+
const key = this.requireKey(input.key);
|
|
757
|
+
const userId = input.userId === null ? null : this.requireUserId(input.userId);
|
|
758
|
+
const where = userId === null ? and(isNull(secrets.userId), eq(secrets.key, key)) : and(eq(secrets.userId, userId), eq(secrets.key, key));
|
|
759
|
+
// Every refusal below is the same words: a caller that can tell "no such
|
|
760
|
+
// sign-in" from "wrong nonce" from "already used" has learned which rows
|
|
761
|
+
// exist, and the handoff route is reachable with the broker secret alone.
|
|
762
|
+
const refused = () => new SecretOAuthError('No managed sign-in is waiting for this handoff');
|
|
763
|
+
const tokens = input.tokens;
|
|
764
|
+
if (typeof tokens?.access_token !== 'string' || !tokens.access_token) {
|
|
765
|
+
throw new InvalidSecretError('the handoff carries no access token');
|
|
766
|
+
}
|
|
767
|
+
// Read, check the nonce, write guarded on what was read; and again when
|
|
768
|
+
// the write misses. A miss with the nonce still on the row is a refresh
|
|
769
|
+
// that rotated the row's tokens meanwhile (a refresh carries the nonce
|
|
770
|
+
// across, see `inFlightOf`), not a second handoff: the broker delivered
|
|
771
|
+
// these tokens once, and refusing them would lose a valid sign-in for
|
|
772
|
+
// good. A row whose nonce is gone or changed refuses on the re-read.
|
|
773
|
+
for (let attempt = 0; ; attempt++) {
|
|
774
|
+
const [row] = await this.db.select().from(secrets).where(where).limit(1);
|
|
775
|
+
if (!row || row.kind !== 'oauth' || !this.managedRefOf(row.oauthMeta)) throw refused();
|
|
776
|
+
let blob: OAuthBlob;
|
|
777
|
+
try {
|
|
778
|
+
blob = this.readBlob(row.valueEncrypted);
|
|
779
|
+
} catch {
|
|
780
|
+
throw refused();
|
|
781
|
+
}
|
|
782
|
+
if (!blob.pendingNonce || typeof input.nonce !== 'string' || !timingSafeStringEqual(input.nonce, blob.pendingNonce)) {
|
|
783
|
+
throw refused();
|
|
784
|
+
}
|
|
785
|
+
const meta = { ...((row.oauthMeta ?? {}) as Record<string, unknown>) };
|
|
786
|
+
// A broker that echoes no `scope` delivered what the sign-in asked for:
|
|
787
|
+
// the scopes the row was opened with stand in, as `pendingScopes` does
|
|
788
|
+
// for the code exchange. Stored as unknown, the grant would count as
|
|
789
|
+
// covering nothing, and the sign-in would read as under-scoped for good.
|
|
790
|
+
// An EMPTY scope is not "none echoed": it is an empty grant, kept as such.
|
|
791
|
+
const requested = Array.isArray(meta.scopes) && meta.scopes.length > 0 ? meta.scopes.map(String).join(' ') : undefined;
|
|
792
|
+
const stored: OAuthTokenSet = {
|
|
793
|
+
access_token: tokens.access_token,
|
|
794
|
+
refresh_token: typeof tokens.refresh_token === 'string' ? tokens.refresh_token : undefined,
|
|
795
|
+
expires_at: typeof tokens.expires_at === 'number' ? tokens.expires_at : undefined,
|
|
796
|
+
token_type: typeof tokens.token_type === 'string' ? tokens.token_type : undefined,
|
|
797
|
+
scope: typeof tokens.scope === 'string' ? tokens.scope : requested,
|
|
798
|
+
};
|
|
799
|
+
// The nonce is one-time: it leaves with the handoff it was minted for,
|
|
800
|
+
// so the same handoff presented twice finds nothing to match.
|
|
801
|
+
const next: OAuthBlob = { tokens: stored };
|
|
802
|
+
if (typeof input.account === 'string' && input.account.trim()) meta.account = input.account.trim().slice(0, MAX_LABEL_LEN);
|
|
803
|
+
// Guarded on the ciphertext read above: two handoffs racing for one
|
|
804
|
+
// nonce cannot both land, whichever the provider sent first wins.
|
|
805
|
+
const landed = await this.db
|
|
806
|
+
.update(secrets)
|
|
807
|
+
.set({ valueEncrypted: this.crypto().encrypt(JSON.stringify(next)), oauthMeta: meta, updatedAt: new Date() })
|
|
808
|
+
.where(and(eq(secrets.id, row.id), eq(secrets.valueEncrypted, row.valueEncrypted)))
|
|
809
|
+
.returning({ id: secrets.id });
|
|
810
|
+
if (!(Array.isArray(landed) && landed.length === 0)) break;
|
|
811
|
+
if (attempt + 1 >= HANDOFF_ATTEMPTS) throw refused();
|
|
812
|
+
}
|
|
813
|
+
this.notifyMutation(userId);
|
|
814
|
+
}
|
|
815
|
+
|
|
360
816
|
/**
|
|
361
817
|
* Decrypt+parse an oauth row's token set once, or undefined if the blob can't be
|
|
362
818
|
* read — a corrupted row or rotated encKey must degrade to "not authorized", not throw.
|
|
@@ -487,20 +943,27 @@ export class DbSecretsVaultService implements ISecretsVaultService {
|
|
|
487
943
|
throw new InvalidSecretError('clientSecret is required');
|
|
488
944
|
}
|
|
489
945
|
// Validate the provider config the same way createOAuth does (SSRF-safe https
|
|
490
|
-
// URLs)
|
|
491
|
-
//
|
|
946
|
+
// URLs) and store it beside the secret as a record of what was saved — what
|
|
947
|
+
// `statusFor` and the setup surfaces read. A declared sign-in does not run
|
|
948
|
+
// from it: `beginToolOAuthByKey` is handed the declaration as it reads now.
|
|
492
949
|
const provider = this.requireProvider({ ...input.provider, clientSecret: input.clientSecret });
|
|
493
950
|
const blob: OAuthBlob = { clientSecret: provider.clientSecret };
|
|
494
951
|
const valueEncrypted = this.crypto().encrypt(JSON.stringify(blob));
|
|
495
952
|
const oauthMeta = this.publicProviderMeta(provider);
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
.
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
953
|
+
// An owner's own app replacing a managed workspace connection: the
|
|
954
|
+
// connection's tokens are revoked with the provider before they go.
|
|
955
|
+
await this.revokedReplacement(and(isNull(secrets.userId), eq(secrets.key, key)), (guard) =>
|
|
956
|
+
this.db
|
|
957
|
+
.insert(secrets)
|
|
958
|
+
.values({ userId: null, key, kind: 'oauth', label: null, valueEncrypted, oauthMeta })
|
|
959
|
+
.onConflictDoUpdate({
|
|
960
|
+
target: secrets.key,
|
|
961
|
+
targetWhere: isNull(secrets.userId),
|
|
962
|
+
set: { kind: 'oauth', valueEncrypted, oauthMeta, updatedAt: new Date() },
|
|
963
|
+
...(guard ? { setWhere: guard } : {}),
|
|
964
|
+
})
|
|
965
|
+
.returning({ id: secrets.id }),
|
|
966
|
+
);
|
|
504
967
|
this.notifyMutation(null);
|
|
505
968
|
}
|
|
506
969
|
|
|
@@ -510,11 +973,15 @@ export class DbSecretsVaultService implements ISecretsVaultService {
|
|
|
510
973
|
redirectUri: string;
|
|
511
974
|
state: string;
|
|
512
975
|
scopes?: string[];
|
|
976
|
+
provider?: DeclaredOAuthProvider;
|
|
513
977
|
}): Promise<{ id: string; url: string }> {
|
|
514
978
|
const userId = this.requireUserId(input.userId);
|
|
515
979
|
const key = this.requireKey(input.key);
|
|
516
|
-
// The shared row holds the owner-set client secret
|
|
517
|
-
//
|
|
980
|
+
// The shared row holds the owner-set client secret. For a client the
|
|
981
|
+
// platform registered itself it also holds the provider configuration, the
|
|
982
|
+
// only description of that client there is; for a declared sign-in the
|
|
983
|
+
// configuration it holds is a record of the save, and `input.provider` (the
|
|
984
|
+
// tool file as it reads now) is what the sign-in follows.
|
|
518
985
|
const [shared] = await this.db
|
|
519
986
|
.select()
|
|
520
987
|
.from(secrets)
|
|
@@ -524,11 +991,46 @@ export class DbSecretsVaultService implements ISecretsVaultService {
|
|
|
524
991
|
throw new SecretOAuthError("This tool's owner hasn't finished setting this up");
|
|
525
992
|
}
|
|
526
993
|
const sharedBlob = this.readBlob(shared.valueEncrypted);
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
994
|
+
let meta: OAuthProviderConfig;
|
|
995
|
+
let oauthMeta: typeof shared.oauthMeta;
|
|
996
|
+
if (input.provider) {
|
|
997
|
+
// A declared client is confidential: without the owner's secret there is
|
|
998
|
+
// nothing to sign in with, whatever the stored row says of itself.
|
|
999
|
+
if (!sharedBlob.clientSecret) {
|
|
1000
|
+
throw new SecretOAuthError("This tool's owner hasn't finished setting this up");
|
|
1001
|
+
}
|
|
1002
|
+
// The save's own validation (https, no private or local address), BEFORE
|
|
1003
|
+
// the caller's row is written or a URL built: a declaration that fails it
|
|
1004
|
+
// sends nothing anywhere. A MANAGED declaration names no client of its
|
|
1005
|
+
// own; reaching here means an owner pinned the deployment's own app on
|
|
1006
|
+
// the shared row, and that app's id is the one the flow runs with.
|
|
1007
|
+
const clientId = input.provider.clientId || this.readMeta(shared.oauthMeta).clientId;
|
|
1008
|
+
meta = this.requireProvider({ ...input.provider, clientId, publicClient: false });
|
|
1009
|
+
oauthMeta = this.publicProviderMeta(meta);
|
|
1010
|
+
// A MOVED PROPERTY, decided on the ticket (OAuth-Sign-In-Follows-The-Live-
|
|
1011
|
+
// Declaration, 2026-10-27): the secret used to be bound to the token
|
|
1012
|
+
// endpoint pinned at its save, so a later file edit could not send it
|
|
1013
|
+
// anywhere else; now it goes where the file says at each start, so a
|
|
1014
|
+
// writer of the tool file can move it (to an https, public host — the
|
|
1015
|
+
// validation above still holds). Nothing refuses that; this line is the
|
|
1016
|
+
// trail an operator reads when a secret went somewhere new.
|
|
1017
|
+
const recorded = (shared.oauthMeta ?? {}) as { tokenUrl?: unknown };
|
|
1018
|
+
if (typeof recorded.tokenUrl === 'string' && recorded.tokenUrl !== meta.tokenUrl) {
|
|
1019
|
+
// Origins only: a path or query is the file's to say, not the log's.
|
|
1020
|
+
const origin = (u: string) => (URL.canParse(u) ? new URL(u).origin : '(unparseable)');
|
|
1021
|
+
log.warn(`sign-in on ${key}: the declared token endpoint differs from the one recorded when the client secret was saved`, {
|
|
1022
|
+
savedOrigin: origin(recorded.tokenUrl),
|
|
1023
|
+
declaredOrigin: origin(meta.tokenUrl),
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
} else {
|
|
1027
|
+
meta = this.readMeta(shared.oauthMeta);
|
|
1028
|
+
oauthMeta = shared.oauthMeta;
|
|
1029
|
+
// A missing secret is "not set up yet" ONLY for confidential clients — a
|
|
1030
|
+
// dynamically-registered PUBLIC client (PKCE-only) never has one.
|
|
1031
|
+
if (!sharedBlob.clientSecret && !meta.publicClient) {
|
|
1032
|
+
throw new SecretOAuthError("This tool's owner hasn't finished setting this up");
|
|
1033
|
+
}
|
|
532
1034
|
}
|
|
533
1035
|
|
|
534
1036
|
// Carry forward any tokens the caller already holds so re-entering the flow
|
|
@@ -543,12 +1045,12 @@ export class DbSecretsVaultService implements ISecretsVaultService {
|
|
|
543
1045
|
// encKey must degrade to "start fresh", not crash the recovery flow — and (b)
|
|
544
1046
|
// they were minted under the SAME provider config we're about to (re)write, or
|
|
545
1047
|
// a refresh_token for a stale client id/secret would silently fail once carried
|
|
546
|
-
// into this row after an owner rotates the shared secret
|
|
1048
|
+
// into this row after an owner rotates the shared secret or edits the
|
|
1049
|
+
// declaration. Compared as values, not as serialised text: the stored side
|
|
1050
|
+
// has been through jsonb, which does not keep key order, and a declared
|
|
1051
|
+
// sign-in's side is built here.
|
|
547
1052
|
let existingTokens: OAuthTokenSet | undefined;
|
|
548
|
-
if (
|
|
549
|
-
existingUserRow?.kind === 'oauth' &&
|
|
550
|
-
JSON.stringify(existingUserRow.oauthMeta) === JSON.stringify(shared.oauthMeta)
|
|
551
|
-
) {
|
|
1053
|
+
if (existingUserRow?.kind === 'oauth' && isDeepStrictEqual(existingUserRow.oauthMeta, oauthMeta)) {
|
|
552
1054
|
try {
|
|
553
1055
|
existingTokens = this.readBlob(existingUserRow.valueEncrypted).tokens;
|
|
554
1056
|
} catch {
|
|
@@ -567,9 +1069,13 @@ export class DbSecretsVaultService implements ISecretsVaultService {
|
|
|
567
1069
|
const requestedScopes = input.scopes && input.scopes.length ? input.scopes : meta.scopes;
|
|
568
1070
|
const pendingScopes = requestedScopes && requestedScopes.length ? requestedScopes.join(' ') : undefined;
|
|
569
1071
|
|
|
570
|
-
// Provision (or reset) the caller's own oauth row for this key from the
|
|
571
|
-
//
|
|
572
|
-
//
|
|
1072
|
+
// Provision (or reset) the caller's own oauth row for this key from the provider
|
|
1073
|
+
// configuration this start uses + the shared secret, preserving any existing
|
|
1074
|
+
// tokens. The row records that configuration because `completeOAuth` and every
|
|
1075
|
+
// later refresh of this grant read it from HERE: the exchange goes to the token
|
|
1076
|
+
// endpoint, under the client id, that the consent was given for. Keyed
|
|
1077
|
+
// `<manual>_<VAR>` so `resolve` (scope 'user') returns the token once sign-in
|
|
1078
|
+
// completes.
|
|
573
1079
|
const blob: OAuthBlob = {
|
|
574
1080
|
clientSecret: sharedBlob.clientSecret,
|
|
575
1081
|
tokens: existingTokens,
|
|
@@ -577,18 +1083,25 @@ export class DbSecretsVaultService implements ISecretsVaultService {
|
|
|
577
1083
|
pendingScopes,
|
|
578
1084
|
};
|
|
579
1085
|
const valueEncrypted = this.crypto().encrypt(JSON.stringify(blob));
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
1086
|
+
// A managed sign-in on the caller's row (an owner pinned their own app
|
|
1087
|
+
// after the person connected through the broker) is replaced by this
|
|
1088
|
+
// start: its tokens are told to the provider first, and the write is
|
|
1089
|
+
// guarded on what the revoke read. Tokens carried over above were minted
|
|
1090
|
+
// under THIS configuration, never a managed one.
|
|
1091
|
+
const row = await this.revokedReplacement(and(eq(secrets.userId, userId), eq(secrets.key, key)), (guard) =>
|
|
1092
|
+
this.db
|
|
1093
|
+
.insert(secrets)
|
|
1094
|
+
.values({ userId, key, kind: 'oauth', label: shared.label, valueEncrypted, oauthMeta })
|
|
1095
|
+
.onConflictDoUpdate({
|
|
1096
|
+
target: [secrets.userId, secrets.key],
|
|
1097
|
+
set: { kind: 'oauth', valueEncrypted, oauthMeta, updatedAt: new Date() },
|
|
1098
|
+
...(guard ? { setWhere: guard } : {}),
|
|
1099
|
+
})
|
|
1100
|
+
.returning(),
|
|
1101
|
+
);
|
|
1102
|
+
|
|
1103
|
+
// Build the consent URL exactly as beginOAuth does, from the configuration
|
|
1104
|
+
// this start uses, with the requested scopes chosen above.
|
|
592
1105
|
const url = new URL(meta.authorizationUrl);
|
|
593
1106
|
url.searchParams.set('response_type', 'code');
|
|
594
1107
|
url.searchParams.set('client_id', meta.clientId);
|
|
@@ -660,10 +1173,16 @@ export class DbSecretsVaultService implements ISecretsVaultService {
|
|
|
660
1173
|
}
|
|
661
1174
|
|
|
662
1175
|
async forceRefresh(userId: string, key: string): Promise<ForcedRefreshOutcome> {
|
|
1176
|
+
// The caller's own row, or — for a variable the manual declares shared
|
|
1177
|
+
// (a managed workspace connection) — the one shared row every caller
|
|
1178
|
+
// resolves. Same tier rule as `resolve`, so a 401 against a shared
|
|
1179
|
+
// token repairs the row the call actually used.
|
|
1180
|
+
const scope = (await this.scopeOf?.(key)) ?? 'admin';
|
|
1181
|
+
const scopeFilter = scope === 'user' ? eq(secrets.userId, userId) : isNull(secrets.userId);
|
|
663
1182
|
const [row] = await this.db
|
|
664
1183
|
.select()
|
|
665
1184
|
.from(secrets)
|
|
666
|
-
.where(and(
|
|
1185
|
+
.where(and(scopeFilter, eq(secrets.key, key)))
|
|
667
1186
|
.limit(1);
|
|
668
1187
|
if (!row || row.kind !== 'oauth') return 'skipped';
|
|
669
1188
|
let blob: OAuthBlob;
|
|
@@ -710,17 +1229,38 @@ export class DbSecretsVaultService implements ISecretsVaultService {
|
|
|
710
1229
|
return this.wipeTokens(row, blob);
|
|
711
1230
|
}
|
|
712
1231
|
const meta = this.readMeta(row.oauthMeta);
|
|
713
|
-
const body = new URLSearchParams({
|
|
714
|
-
grant_type: 'refresh_token',
|
|
715
|
-
refresh_token: tokens.refresh_token,
|
|
716
|
-
client_id: meta.clientId,
|
|
717
|
-
});
|
|
718
|
-
if (blob.clientSecret) body.set('client_secret', blob.clientSecret);
|
|
719
|
-
if (meta.resource) body.set('resource', meta.resource);
|
|
720
|
-
|
|
721
1232
|
let refreshed: OAuthTokenSet;
|
|
722
1233
|
try {
|
|
723
|
-
|
|
1234
|
+
if (meta.managed) {
|
|
1235
|
+
// A managed sign-in renews through the broker, which holds the
|
|
1236
|
+
// client secret this deployment never sees. No broker wired is an
|
|
1237
|
+
// outage from here, not a dead grant: the stale token is served and
|
|
1238
|
+
// the next call asks again.
|
|
1239
|
+
if (!this.managedRefresher) return { outcome: 'transient' };
|
|
1240
|
+
// Bounded like a revocation: a broker that never answers is an outage
|
|
1241
|
+
// (the stale token is served) and must not hold every resolution.
|
|
1242
|
+
const delivered = await bounded(
|
|
1243
|
+
this.managedRefresher.refresh(meta.managed, tokens.refresh_token),
|
|
1244
|
+
REFRESH_TIMEOUT_MS,
|
|
1245
|
+
'the broker refresh',
|
|
1246
|
+
);
|
|
1247
|
+
refreshed = {
|
|
1248
|
+
access_token: delivered.access_token,
|
|
1249
|
+
refresh_token: delivered.refresh_token,
|
|
1250
|
+
expires_at: delivered.expires_at,
|
|
1251
|
+
token_type: delivered.token_type,
|
|
1252
|
+
scope: delivered.scope,
|
|
1253
|
+
};
|
|
1254
|
+
} else {
|
|
1255
|
+
const body = new URLSearchParams({
|
|
1256
|
+
grant_type: 'refresh_token',
|
|
1257
|
+
refresh_token: tokens.refresh_token,
|
|
1258
|
+
client_id: meta.clientId,
|
|
1259
|
+
});
|
|
1260
|
+
if (blob.clientSecret) body.set('client_secret', blob.clientSecret);
|
|
1261
|
+
if (meta.resource) body.set('resource', meta.resource);
|
|
1262
|
+
refreshed = await this.tokenRequest(meta.tokenUrl, body);
|
|
1263
|
+
}
|
|
724
1264
|
} catch (err) {
|
|
725
1265
|
// A DEFINITIVE provider rejection (400/401 — invalid_grant and friends)
|
|
726
1266
|
// means the grant is dead: no future refresh will revive it. Wipe the
|
|
@@ -732,7 +1272,23 @@ export class DbSecretsVaultService implements ISecretsVaultService {
|
|
|
732
1272
|
err instanceof SecretOAuthError &&
|
|
733
1273
|
(err.providerStatus === 400 || err.providerStatus === 401)
|
|
734
1274
|
) {
|
|
735
|
-
|
|
1275
|
+
// The only trace of a sign-in that silently turns into "not connected":
|
|
1276
|
+
// say which one, the provider's reason, and what the guarded wipe did —
|
|
1277
|
+
// a concurrent refresh may have rotated the tokens first, in which case
|
|
1278
|
+
// nothing was wiped and the rotated grant is served.
|
|
1279
|
+
const result = await this.wipeTokens(row, blob);
|
|
1280
|
+
const did = result.wiped
|
|
1281
|
+
? 'sign-in wiped'
|
|
1282
|
+
: result.outcome === 'refreshed'
|
|
1283
|
+
? 'the row had been rotated by a concurrent refresh meanwhile; the rotated sign-in is kept'
|
|
1284
|
+
: 'the row had been removed or replaced meanwhile; nothing wiped';
|
|
1285
|
+
// A managed row's refusal came from the refresher, whose words are its
|
|
1286
|
+
// own (a port may put the refresh token it was handed in them): only
|
|
1287
|
+
// the status it reported is logged. The vault's own token request
|
|
1288
|
+
// writes fixed sentences, which are safe to repeat.
|
|
1289
|
+
const why = meta.managed ? `the broker refused it (HTTP ${err.providerStatus ?? 'unknown'})` : err.message;
|
|
1290
|
+
log.warn(`refresh refused for "${row.key}", ${did}: ${why}`);
|
|
1291
|
+
return result;
|
|
736
1292
|
}
|
|
737
1293
|
return { outcome: 'transient' };
|
|
738
1294
|
}
|
|
@@ -743,7 +1299,10 @@ export class DbSecretsVaultService implements ISecretsVaultService {
|
|
|
743
1299
|
// rule as the exchange: only an ABSENT field means "unchanged"; an echoed
|
|
744
1300
|
// value, empty included, is the provider's word.
|
|
745
1301
|
if (refreshed.scope === undefined) refreshed.scope = tokens.scope;
|
|
746
|
-
|
|
1302
|
+
// A sign-in in flight on this row (a managed reconnect waiting for its
|
|
1303
|
+
// handoff, a consent waiting for its exchange) survives the refresh: the
|
|
1304
|
+
// nonce or verifier it minted is what that handoff must match.
|
|
1305
|
+
const next: OAuthBlob = { clientSecret: blob.clientSecret, tokens: refreshed, ...this.inFlightOf(blob) };
|
|
747
1306
|
|
|
748
1307
|
// Optimistic concurrency: only persist if the stored ciphertext is unchanged,
|
|
749
1308
|
// so a concurrent refresh on the same row doesn't clobber (Microsoft pattern).
|
|
@@ -790,12 +1349,30 @@ export class DbSecretsVaultService implements ISecretsVaultService {
|
|
|
790
1349
|
}
|
|
791
1350
|
}
|
|
792
1351
|
|
|
793
|
-
/**
|
|
1352
|
+
/**
|
|
1353
|
+
* What a row holds for a sign-in IN FLIGHT: the one-time nonce a managed
|
|
1354
|
+
* handoff must echo, the PKCE verifier and requested scopes a code exchange
|
|
1355
|
+
* must present. A refresh or a wipe rewrites the row's tokens, never these:
|
|
1356
|
+
* dropping them would refuse the handoff or the exchange that is about to
|
|
1357
|
+
* land, for a reason the person could not see.
|
|
1358
|
+
*/
|
|
1359
|
+
private inFlightOf(blob: OAuthBlob): Pick<OAuthBlob, 'pendingNonce' | 'pendingVerifier' | 'pendingScopes'> {
|
|
1360
|
+
return {
|
|
1361
|
+
...(blob.pendingNonce ? { pendingNonce: blob.pendingNonce } : {}),
|
|
1362
|
+
...(blob.pendingVerifier ? { pendingVerifier: blob.pendingVerifier } : {}),
|
|
1363
|
+
...(blob.pendingScopes ? { pendingScopes: blob.pendingScopes } : {}),
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
/** Drop a dead grant's token set, keeping the client secret and any sign-in in flight. See {@link refreshRow}. */
|
|
794
1368
|
private async wipeTokens(
|
|
795
1369
|
row: typeof secrets.$inferSelect,
|
|
796
1370
|
blob: OAuthBlob,
|
|
797
|
-
): Promise<
|
|
798
|
-
|
|
1371
|
+
): Promise<
|
|
1372
|
+
| { outcome: 'refreshed'; accessToken: string; wiped: false }
|
|
1373
|
+
| { outcome: 'rejected'; accessToken: null; wiped: boolean }
|
|
1374
|
+
> {
|
|
1375
|
+
const next: OAuthBlob = { clientSecret: blob.clientSecret, ...this.inFlightOf(blob) };
|
|
799
1376
|
// Guarded on the ciphertext we read, so a concurrent refresh that
|
|
800
1377
|
// already persisted ROTATED tokens can't be wiped by our stale
|
|
801
1378
|
// failure. When that guard trips (0 rows), the failure was against a
|
|
@@ -807,13 +1384,17 @@ export class DbSecretsVaultService implements ISecretsVaultService {
|
|
|
807
1384
|
.where(and(eq(secrets.id, row.id), eq(secrets.valueEncrypted, row.valueEncrypted)))
|
|
808
1385
|
.returning({ id: secrets.id });
|
|
809
1386
|
if (Array.isArray(wiped) && wiped.length === 0) {
|
|
1387
|
+
// `wiped: false` either way: this write matched nothing, and the caller
|
|
1388
|
+
// reports what it did, not what it set out to do.
|
|
810
1389
|
const fresh = await this.currentAccessToken(row.id);
|
|
811
|
-
return fresh
|
|
1390
|
+
return fresh
|
|
1391
|
+
? { outcome: 'refreshed', accessToken: fresh, wiped: false }
|
|
1392
|
+
: { outcome: 'rejected', accessToken: null, wiped: false };
|
|
812
1393
|
}
|
|
813
1394
|
// The sign-in just became not-connected — let credential-validity caches
|
|
814
1395
|
// (pooled downstream connections dialed with the dead token) drop it now.
|
|
815
1396
|
this.notifyMutation(row.userId);
|
|
816
|
-
return { outcome: 'rejected', accessToken: null };
|
|
1397
|
+
return { outcome: 'rejected', accessToken: null, wiped: true };
|
|
817
1398
|
}
|
|
818
1399
|
|
|
819
1400
|
async putSharedOAuthProvider(input: {
|
|
@@ -829,17 +1410,21 @@ export class DbSecretsVaultService implements ISecretsVaultService {
|
|
|
829
1410
|
const blob: OAuthBlob = { clientSecret: provider.clientSecret };
|
|
830
1411
|
const valueEncrypted = this.crypto().encrypt(JSON.stringify(blob));
|
|
831
1412
|
const oauthMeta = this.publicProviderMeta(provider);
|
|
832
|
-
await this.
|
|
833
|
-
.
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
1413
|
+
await this.revokedReplacement(and(isNull(secrets.userId), eq(secrets.key, key)), (guard) =>
|
|
1414
|
+
this.db
|
|
1415
|
+
.insert(secrets)
|
|
1416
|
+
.values({ userId: null, key, kind: 'oauth', label, valueEncrypted, oauthMeta })
|
|
1417
|
+
// Preserve an existing row's label on re-upsert (matches
|
|
1418
|
+
// `putSharedOAuthClientSecret`): the optional input label is only for the
|
|
1419
|
+
// FIRST write, so a later re-registration can't null out an owner label.
|
|
1420
|
+
.onConflictDoUpdate({
|
|
1421
|
+
target: secrets.key,
|
|
1422
|
+
targetWhere: isNull(secrets.userId),
|
|
1423
|
+
set: { kind: 'oauth', valueEncrypted, oauthMeta, updatedAt: new Date() },
|
|
1424
|
+
...(guard ? { setWhere: guard } : {}),
|
|
1425
|
+
})
|
|
1426
|
+
.returning({ id: secrets.id }),
|
|
1427
|
+
);
|
|
843
1428
|
this.notifyMutation(null);
|
|
844
1429
|
}
|
|
845
1430
|
|
|
@@ -887,7 +1472,15 @@ export class DbSecretsVaultService implements ISecretsVaultService {
|
|
|
887
1472
|
throw new SecretOAuthError(msg);
|
|
888
1473
|
}
|
|
889
1474
|
if (!res.ok) {
|
|
890
|
-
|
|
1475
|
+
// The provider's error CODE rides in the message: the callback route and
|
|
1476
|
+
// the refresh path log that message, and it is the one line that says
|
|
1477
|
+
// WHY a sign-in failed (a client the provider does not accept, a refused
|
|
1478
|
+
// authorization code). Best effort: the STATUS is the contract (the
|
|
1479
|
+
// refresh path reads 400/401 off it to retire a dead grant), and a body
|
|
1480
|
+
// that fails to read must not turn a definitive refusal into an
|
|
1481
|
+
// unclassified error.
|
|
1482
|
+
const code = tokenEndpointErrorCode(await readCapped(res, TOKEN_ERROR_BODY_MAX_BYTES).catch(() => ''));
|
|
1483
|
+
throw new SecretOAuthError(describeTokenEndpointRefusal(res.status, code), res.status);
|
|
891
1484
|
}
|
|
892
1485
|
const json = (await res.json()) as Record<string, unknown>;
|
|
893
1486
|
const accessToken = typeof json.access_token === 'string' ? json.access_token : '';
|
|
@@ -923,18 +1516,22 @@ export class DbSecretsVaultService implements ISecretsVaultService {
|
|
|
923
1516
|
}
|
|
924
1517
|
|
|
925
1518
|
private readMeta(raw: unknown): Required<Pick<OAuthProviderConfig, 'authorizationUrl' | 'tokenUrl' | 'clientId'>> &
|
|
926
|
-
Pick<OAuthProviderConfig, 'scopes' | 'authParams' | 'pkce' | 'publicClient' | 'resource'> {
|
|
1519
|
+
Pick<OAuthProviderConfig, 'scopes' | 'authParams' | 'pkce' | 'publicClient' | 'resource' | 'managed'> {
|
|
927
1520
|
const meta = (raw ?? {}) as Record<string, unknown>;
|
|
928
1521
|
const authorizationUrl = typeof meta.authorizationUrl === 'string' ? meta.authorizationUrl : '';
|
|
929
1522
|
const tokenUrl = typeof meta.tokenUrl === 'string' ? meta.tokenUrl : '';
|
|
930
1523
|
const clientId = typeof meta.clientId === 'string' ? meta.clientId : '';
|
|
931
|
-
|
|
1524
|
+
const managed = this.managedRefOf(meta) ?? undefined;
|
|
1525
|
+
// A managed row has no client id of its own (the broker's app is the
|
|
1526
|
+
// client); everything else must name one, or no flow can run from it.
|
|
1527
|
+
if (!authorizationUrl || !tokenUrl || (!clientId && !managed)) {
|
|
932
1528
|
throw new SecretOAuthError('OAuth secret is missing provider configuration');
|
|
933
1529
|
}
|
|
934
1530
|
return {
|
|
935
1531
|
authorizationUrl,
|
|
936
1532
|
tokenUrl,
|
|
937
1533
|
clientId,
|
|
1534
|
+
managed,
|
|
938
1535
|
scopes: Array.isArray(meta.scopes) ? meta.scopes.map(String) : undefined,
|
|
939
1536
|
authParams:
|
|
940
1537
|
meta.authParams && typeof meta.authParams === 'object'
|
|
@@ -956,16 +1553,27 @@ export class DbSecretsVaultService implements ISecretsVaultService {
|
|
|
956
1553
|
...(p.pkce ? { pkce: true } : {}),
|
|
957
1554
|
...(p.publicClient ? { publicClient: true } : {}),
|
|
958
1555
|
...(p.resource ? { resource: p.resource } : {}),
|
|
1556
|
+
...(p.managed ? { managed: { connectorId: p.managed.connectorId, scopeSet: p.managed.scopeSet } } : {}),
|
|
1557
|
+
...(p.account ? { account: p.account } : {}),
|
|
959
1558
|
};
|
|
960
1559
|
}
|
|
961
1560
|
|
|
962
|
-
|
|
1561
|
+
/**
|
|
1562
|
+
* @param opts.managed Accept a MANAGED provider (no client id of its own,
|
|
1563
|
+
* a connector reference instead). Only the managed sign-in paths pass it:
|
|
1564
|
+
* everywhere else a provider needs a client id, and a `managed` field a
|
|
1565
|
+
* caller slipped in is dropped rather than making the row one.
|
|
1566
|
+
*/
|
|
1567
|
+
private requireProvider(p: unknown, opts: { managed?: boolean } = {}): OAuthProviderConfig {
|
|
963
1568
|
if (!p || typeof p !== 'object') throw new InvalidSecretError('provider is required');
|
|
964
1569
|
const cfg = p as Record<string, unknown>;
|
|
965
1570
|
const authorizationUrl = this.requireUrl(cfg.authorizationUrl, 'authorizationUrl');
|
|
966
1571
|
const tokenUrl = this.requireUrl(cfg.tokenUrl, 'tokenUrl');
|
|
967
|
-
const
|
|
968
|
-
|
|
1572
|
+
const managed = opts.managed ? (this.managedRefOf(cfg) ?? undefined) : undefined;
|
|
1573
|
+
// A managed sign-in has no client of its own: whatever id a caller sends
|
|
1574
|
+
// beside the connector reference is not stored, so no row can claim one.
|
|
1575
|
+
const clientId = managed ? '' : typeof cfg.clientId === 'string' ? cfg.clientId.trim() : '';
|
|
1576
|
+
if (!clientId && !managed) throw new InvalidSecretError('clientId is required');
|
|
969
1577
|
const scopes = Array.isArray(cfg.scopes) ? cfg.scopes.map(String) : undefined;
|
|
970
1578
|
const authParams =
|
|
971
1579
|
cfg.authParams && typeof cfg.authParams === 'object'
|
|
@@ -986,6 +1594,8 @@ export class DbSecretsVaultService implements ISecretsVaultService {
|
|
|
986
1594
|
typeof cfg.resource === 'string' && cfg.resource.trim()
|
|
987
1595
|
? this.requireUrl(cfg.resource, 'resource')
|
|
988
1596
|
: undefined,
|
|
1597
|
+
managed,
|
|
1598
|
+
account: typeof cfg.account === 'string' && cfg.account.trim() ? cfg.account.trim().slice(0, MAX_LABEL_LEN) : undefined,
|
|
989
1599
|
};
|
|
990
1600
|
}
|
|
991
1601
|
|