@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,11 +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
|
const log = logger('vault');
|
|
4
5
|
import { and, desc, eq, inArray, isNull, like, or } from 'drizzle-orm';
|
|
5
|
-
import { secrets } from '../database/index.js';
|
|
6
|
+
import { pgErrorCode, secrets } from '../database/index.js';
|
|
6
7
|
import { TokenCrypto } from '../../shared/token-crypto.js';
|
|
7
8
|
import { assertSafeFetchUrl } from '../../shared/ssrf.js';
|
|
9
|
+
import { readCapped } from '../../shared/read-capped.js';
|
|
10
|
+
import { standardTokenErrorCode } from '../../shared/oauth-token-error.js';
|
|
8
11
|
import { isKeyInNamespace, InvalidSecretError, SecretNotFoundError, SecretOAuthError, } from './secrets-vault.contract.js';
|
|
12
|
+
import { timingSafeStringEqual } from '../auth/index.js';
|
|
9
13
|
const MAX_KEY_LEN = 200;
|
|
10
14
|
const MAX_LABEL_LEN = 200;
|
|
11
15
|
const MAX_VALUE_LEN = 100_000;
|
|
@@ -13,6 +17,99 @@ const MAX_VALUE_LEN = 100_000;
|
|
|
13
17
|
const REFRESH_SKEW_MS = 60_000;
|
|
14
18
|
/** Upper bound on a token-endpoint round-trip, so a hung provider can't block callers. */
|
|
15
19
|
const TOKEN_REQUEST_TIMEOUT_MS = 10_000;
|
|
20
|
+
/** Upper bound on telling a provider a managed sign-in is over, so a deletion never waits on it. */
|
|
21
|
+
const REVOKE_TIMEOUT_MS = 10_000;
|
|
22
|
+
/** Upper bound on a broker renewing a managed token: past it the stale token is served and the next call asks again. */
|
|
23
|
+
const REFRESH_TIMEOUT_MS = 15_000;
|
|
24
|
+
/** How many times a guarded replacement re-reads a row that changed under its revoke before giving up. */
|
|
25
|
+
const REPLACE_ATTEMPTS = 3;
|
|
26
|
+
/** How many times a handoff re-reads a row a refresh rotated under it (the nonce rides along) before refusing. */
|
|
27
|
+
const HANDOFF_ATTEMPTS = 3;
|
|
28
|
+
/** `work`, or a rejection once `ms` have passed: a handler that never settles must not hold the vault. */
|
|
29
|
+
async function bounded(work, ms, what) {
|
|
30
|
+
let timer;
|
|
31
|
+
try {
|
|
32
|
+
return await Promise.race([
|
|
33
|
+
work,
|
|
34
|
+
new Promise((_, reject) => {
|
|
35
|
+
timer = setTimeout(() => reject(new Error(`${what} did not settle within ${ms}ms`)), ms);
|
|
36
|
+
}),
|
|
37
|
+
]);
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
if (timer)
|
|
41
|
+
clearTimeout(timer);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** How often a managed sign-in's opening is retried against a row that keeps changing under it. */
|
|
45
|
+
const BEGIN_MANAGED_ATTEMPTS = 3;
|
|
46
|
+
/** How much of a refusing token endpoint's body is read for its error code: an OAuth error object, never a whole error page. */
|
|
47
|
+
const TOKEN_ERROR_BODY_MAX_BYTES = 2048;
|
|
48
|
+
// Hexis's own sentences for a refused token request: what to change, in our
|
|
49
|
+
// words, never the provider's.
|
|
50
|
+
const HINT_CLIENT = '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.';
|
|
51
|
+
const HINT_GRANT = '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.';
|
|
52
|
+
const HINT_SCOPE = "The provider refused the scopes this sign-in asks for. The tool owner should check the declared scopes against the provider's app.";
|
|
53
|
+
const HINT_UNAUTHORIZED = "The provider does not allow this client to use this grant. The tool owner should check the provider's app settings.";
|
|
54
|
+
const HINT_REQUEST = 'The provider found the token request malformed. The tool owner should check the declared endpoints and parameters.';
|
|
55
|
+
const HINT_PROVIDER = 'The provider could not serve the token request. Try again later.';
|
|
56
|
+
/**
|
|
57
|
+
* Hexis's sentence for each error code a refusing token endpoint may answer:
|
|
58
|
+
* the standard ones (`shared/oauth-token-error.ts` is the one list of those,
|
|
59
|
+
* read here and by the OIDC configuration check) in `error`, and the ones
|
|
60
|
+
* HubSpot's token endpoint answers in its `status` field. Only a code from
|
|
61
|
+
* those lists is ever taken, exactly, so no other string a provider puts in a
|
|
62
|
+
* field — a secret, a secret with a suffix, free text — can reach a message.
|
|
63
|
+
* Nothing else of the body is ever taken: no description, no message, no first
|
|
64
|
+
* line. That is why a message built here needs no scrubbing, cutting or
|
|
65
|
+
* decoding, and why every spelling a secret could reach a log in (escaped,
|
|
66
|
+
* encoded, cut) stays inexpressible. A standard code without a sentence here
|
|
67
|
+
* (`access_denied`) is named and left unexplained.
|
|
68
|
+
*/
|
|
69
|
+
const TOKEN_ERROR_HINTS = new Map([
|
|
70
|
+
['invalid_request', HINT_REQUEST],
|
|
71
|
+
['invalid_client', HINT_CLIENT],
|
|
72
|
+
['invalid_grant', HINT_GRANT],
|
|
73
|
+
['unauthorized_client', HINT_UNAUTHORIZED],
|
|
74
|
+
['unsupported_grant_type', HINT_UNAUTHORIZED],
|
|
75
|
+
['invalid_scope', HINT_SCOPE],
|
|
76
|
+
['server_error', HINT_PROVIDER],
|
|
77
|
+
['temporarily_unavailable', HINT_PROVIDER],
|
|
78
|
+
['BAD_CLIENT_ID', HINT_CLIENT],
|
|
79
|
+
['BAD_CLIENT_SECRET', HINT_CLIENT],
|
|
80
|
+
['BAD_AUTH_CODE', HINT_GRANT],
|
|
81
|
+
['BAD_REDIRECT_URI', HINT_GRANT],
|
|
82
|
+
]);
|
|
83
|
+
/** The codes HubSpot's token endpoint answers in `status` instead of a standard `error`. */
|
|
84
|
+
const HUBSPOT_STATUS_CODES = new Set(['BAD_CLIENT_ID', 'BAD_CLIENT_SECRET', 'BAD_AUTH_CODE', 'BAD_REDIRECT_URI']);
|
|
85
|
+
/**
|
|
86
|
+
* The known error code a refusing token endpoint answered, or null: a standard
|
|
87
|
+
* `error` (RFC 6749 §5.2), else HubSpot's own `status`, when it is exactly one
|
|
88
|
+
* of the codes known. Without even the code, a bare "HTTP 400" cannot tell a
|
|
89
|
+
* client the provider does not accept from a refused grant.
|
|
90
|
+
*/
|
|
91
|
+
export function tokenEndpointErrorCode(bodyText) {
|
|
92
|
+
const standard = standardTokenErrorCode(bodyText);
|
|
93
|
+
if (standard !== null)
|
|
94
|
+
return standard;
|
|
95
|
+
let json;
|
|
96
|
+
try {
|
|
97
|
+
json = JSON.parse(bodyText);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
if (!json || typeof json !== 'object' || Array.isArray(json))
|
|
103
|
+
return null;
|
|
104
|
+
const { status } = json;
|
|
105
|
+
return typeof status === 'string' && HUBSPOT_STATUS_CODES.has(status) ? status : null;
|
|
106
|
+
}
|
|
107
|
+
/** The refusal in one message: the status, the known code when there is one, and Hexis's sentence for it. */
|
|
108
|
+
export function describeTokenEndpointRefusal(status, code) {
|
|
109
|
+
const base = `Token endpoint returned HTTP ${status}`;
|
|
110
|
+
const hint = code === null ? undefined : TOKEN_ERROR_HINTS.get(code);
|
|
111
|
+
return hint ? `${base} (${code}). ${hint}` : base;
|
|
112
|
+
}
|
|
16
113
|
/**
|
|
17
114
|
* How a row is stored, or `null` when there is no row — the column is a plain
|
|
18
115
|
* string, and anything that is not `oauth` is a stored value (`toSummary` reads
|
|
@@ -41,6 +138,19 @@ export class DbSecretsVaultService {
|
|
|
41
138
|
now;
|
|
42
139
|
scopeOf;
|
|
43
140
|
cryptoInstance = null;
|
|
141
|
+
/**
|
|
142
|
+
* How a MANAGED sign-in's tokens are renewed and revoked — through the
|
|
143
|
+
* broker and the provider respectively. Setter-injected: both depend on
|
|
144
|
+
* the connector catalog, which is built after this service. Without them a
|
|
145
|
+
* managed token that expires is served stale (a transient, retried on the
|
|
146
|
+
* next call) and a disconnect deletes without revoking.
|
|
147
|
+
*/
|
|
148
|
+
managedRefresher;
|
|
149
|
+
managedRevoker;
|
|
150
|
+
setManagedTokenHandlers(handlers) {
|
|
151
|
+
this.managedRefresher = handlers.refresher;
|
|
152
|
+
this.managedRevoker = handlers.revoker;
|
|
153
|
+
}
|
|
44
154
|
constructor(db,
|
|
45
155
|
/** 32-byte AES key (hex/base64); empty disables secret read/write with a clear error. */
|
|
46
156
|
encKey, now = Date.now,
|
|
@@ -108,15 +218,18 @@ export class DbSecretsVaultService {
|
|
|
108
218
|
}
|
|
109
219
|
const label = this.normaliseLabel(input.label);
|
|
110
220
|
const valueEncrypted = this.crypto().encrypt(input.value);
|
|
221
|
+
// The caller's row may hold a managed sign-in: its tokens are told to the
|
|
222
|
+
// provider before this value replaces them, as on every replacement.
|
|
111
223
|
// Upsert on (user_id, key): re-saving a key replaces its value in place.
|
|
112
|
-
const
|
|
224
|
+
const row = await this.revokedReplacement(and(eq(secrets.userId, userId), eq(secrets.key, key)), (guard) => this.db
|
|
113
225
|
.insert(secrets)
|
|
114
226
|
.values({ userId, key, kind: 'static', label, valueEncrypted, oauthMeta: null })
|
|
115
227
|
.onConflictDoUpdate({
|
|
116
228
|
target: [secrets.userId, secrets.key],
|
|
117
229
|
set: { kind: 'static', label, valueEncrypted, oauthMeta: null, updatedAt: new Date() },
|
|
230
|
+
...(guard ? { setWhere: guard } : {}),
|
|
118
231
|
})
|
|
119
|
-
.returning();
|
|
232
|
+
.returning());
|
|
120
233
|
this.notifyMutation(userId);
|
|
121
234
|
return this.toSummary(row);
|
|
122
235
|
}
|
|
@@ -128,7 +241,9 @@ export class DbSecretsVaultService {
|
|
|
128
241
|
const blob = { clientSecret: provider.clientSecret };
|
|
129
242
|
const valueEncrypted = this.crypto().encrypt(JSON.stringify(blob));
|
|
130
243
|
const oauthMeta = this.publicProviderMeta(provider);
|
|
131
|
-
|
|
244
|
+
// A managed sign-in under this key loses its tokens here: the provider is
|
|
245
|
+
// told first, as on every replacement.
|
|
246
|
+
const row = await this.revokedReplacement(and(eq(secrets.userId, userId), eq(secrets.key, key)), (guard) => this.db
|
|
132
247
|
.insert(secrets)
|
|
133
248
|
.values({ userId, key, kind: 'oauth', label, valueEncrypted, oauthMeta })
|
|
134
249
|
.onConflictDoUpdate({
|
|
@@ -136,17 +251,15 @@ export class DbSecretsVaultService {
|
|
|
136
251
|
// Replacing an oauth secret's provider config drops any existing tokens
|
|
137
252
|
// (they belonged to the old client) — the user must re-authorize.
|
|
138
253
|
set: { kind: 'oauth', label, valueEncrypted, oauthMeta, updatedAt: new Date() },
|
|
254
|
+
...(guard ? { setWhere: guard } : {}),
|
|
139
255
|
})
|
|
140
|
-
.returning();
|
|
256
|
+
.returning());
|
|
141
257
|
this.notifyMutation(userId);
|
|
142
258
|
return this.toSummary(row);
|
|
143
259
|
}
|
|
144
260
|
async remove(userId, id) {
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
.where(and(eq(secrets.id, id), eq(secrets.userId, userId)))
|
|
148
|
-
.returning({ id: secrets.id });
|
|
149
|
-
if (res.length === 0)
|
|
261
|
+
const gone = await this.revokeThenDelete(and(eq(secrets.id, id), eq(secrets.userId, userId)));
|
|
262
|
+
if (gone.length === 0)
|
|
150
263
|
throw new SecretNotFoundError(id);
|
|
151
264
|
this.notifyMutation(userId);
|
|
152
265
|
}
|
|
@@ -160,38 +273,186 @@ export class DbSecretsVaultService {
|
|
|
160
273
|
}
|
|
161
274
|
const label = this.normaliseLabel(input.label);
|
|
162
275
|
const valueEncrypted = this.crypto().encrypt(input.value);
|
|
276
|
+
// A shared row that held a managed workspace connection is told to its
|
|
277
|
+
// provider before this value replaces it; the tokens would otherwise go
|
|
278
|
+
// unrevoked with the row's kind.
|
|
163
279
|
// Upsert the ONE shared row for this key (user_id NULL). The conflict target
|
|
164
280
|
// is the partial unique index `secrets_shared_key_unq` — hence `targetWhere`.
|
|
165
|
-
const
|
|
281
|
+
const row = await this.revokedReplacement(and(isNull(secrets.userId), eq(secrets.key, key)), (guard) => this.db
|
|
166
282
|
.insert(secrets)
|
|
167
283
|
.values({ userId: null, key, kind: 'static', label, valueEncrypted, oauthMeta: null })
|
|
168
284
|
.onConflictDoUpdate({
|
|
169
285
|
target: secrets.key,
|
|
170
286
|
targetWhere: isNull(secrets.userId),
|
|
171
287
|
set: { kind: 'static', label, valueEncrypted, oauthMeta: null, updatedAt: new Date() },
|
|
288
|
+
...(guard ? { setWhere: guard } : {}),
|
|
172
289
|
})
|
|
173
|
-
.returning();
|
|
290
|
+
.returning());
|
|
174
291
|
this.notifyMutation(null);
|
|
175
292
|
return this.toSummary(row);
|
|
176
293
|
}
|
|
177
294
|
async removeShared(key) {
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
.where(and(isNull(secrets.userId), eq(secrets.key, key)))
|
|
181
|
-
.returning({ id: secrets.id });
|
|
182
|
-
if (res.length === 0)
|
|
295
|
+
const gone = await this.revokeThenDelete(and(isNull(secrets.userId), eq(secrets.key, key)));
|
|
296
|
+
if (gone.length === 0)
|
|
183
297
|
throw new SecretNotFoundError(key);
|
|
184
298
|
this.notifyMutation(null);
|
|
185
299
|
}
|
|
186
300
|
async removeUserByKey(userId, key) {
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
.where(and(eq(secrets.userId, userId), eq(secrets.key, key)))
|
|
190
|
-
.returning({ id: secrets.id });
|
|
191
|
-
if (res.length === 0)
|
|
301
|
+
const gone = await this.revokeThenDelete(and(eq(secrets.userId, userId), eq(secrets.key, key)));
|
|
302
|
+
if (gone.length === 0)
|
|
192
303
|
throw new SecretNotFoundError(key);
|
|
193
304
|
this.notifyMutation(userId);
|
|
194
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* Delete the rows `where` names, each told to its provider first when it
|
|
308
|
+
* is a managed sign-in with tokens (see {@link revokeRow}). The delete is
|
|
309
|
+
* guarded on the ciphertext the revoke read: a handoff or a refresh landing
|
|
310
|
+
* in between has replaced the tokens, and deleting THOSE unrevoked is the
|
|
311
|
+
* leak the revoke exists to prevent. A miss re-reads the row and goes
|
|
312
|
+
* again, bounded; what could not be settled in those passes is left
|
|
313
|
+
* standing rather than deleted blind.
|
|
314
|
+
*/
|
|
315
|
+
async revokeThenDelete(where) {
|
|
316
|
+
// No revoker, nothing to read first: one plain delete, as every removal
|
|
317
|
+
// was before managed sign-ins, and no miss to re-read for.
|
|
318
|
+
if (!this.managedRevoker) {
|
|
319
|
+
return this.db.delete(secrets).where(where).returning({ id: secrets.id, userId: secrets.userId, kind: secrets.kind });
|
|
320
|
+
}
|
|
321
|
+
const gone = [];
|
|
322
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
323
|
+
const rows = await this.db.select().from(secrets).where(where);
|
|
324
|
+
if (rows.length === 0)
|
|
325
|
+
break;
|
|
326
|
+
// Every row's revocation at once, so a bulk delete (a namespace, a
|
|
327
|
+
// person's connections) waits one revocation's bound, not one per row.
|
|
328
|
+
await Promise.all(rows.map((row) => this.revokeRow(row)));
|
|
329
|
+
let missed = false;
|
|
330
|
+
for (const row of rows) {
|
|
331
|
+
const deleted = await this.db
|
|
332
|
+
.delete(secrets)
|
|
333
|
+
.where(and(eq(secrets.id, row.id), eq(secrets.valueEncrypted, row.valueEncrypted)))
|
|
334
|
+
.returning({ id: secrets.id, userId: secrets.userId, kind: secrets.kind });
|
|
335
|
+
if (deleted.length === 0)
|
|
336
|
+
missed = true;
|
|
337
|
+
else
|
|
338
|
+
gone.push(...deleted);
|
|
339
|
+
}
|
|
340
|
+
if (!missed)
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
return gone;
|
|
344
|
+
}
|
|
345
|
+
async removeManagedRowsOfUser(userId, within) {
|
|
346
|
+
if (within)
|
|
347
|
+
return this.removeManagedRowsLocked(userId, within);
|
|
348
|
+
// Every oauth row of the user whose meta names a managed app. Read
|
|
349
|
+
// first (the managed mark lives in JSON the WHERE cannot see), revoked,
|
|
350
|
+
// then deleted by id; the per-user filter on the delete is what keeps a
|
|
351
|
+
// row read for this user from ever deleting another's.
|
|
352
|
+
const rows = await this.db
|
|
353
|
+
.select()
|
|
354
|
+
.from(secrets)
|
|
355
|
+
.where(and(eq(secrets.userId, userId), eq(secrets.kind, 'oauth')));
|
|
356
|
+
const managed = rows.filter((r) => this.managedRefOf(r.oauthMeta) !== null);
|
|
357
|
+
if (managed.length === 0)
|
|
358
|
+
return 0;
|
|
359
|
+
const deleted = await this.revokeThenDelete(and(eq(secrets.userId, userId), inArray(secrets.id, managed.map((r) => r.id))));
|
|
360
|
+
if (deleted.length > 0)
|
|
361
|
+
this.notifyMutation(userId);
|
|
362
|
+
// A row the guarded delete could not settle (its tokens kept changing
|
|
363
|
+
// under the revoke) is still here, unrevoked. The caller is told rather
|
|
364
|
+
// than left to believe the person holds nothing: an erasure refuses and
|
|
365
|
+
// is retried, a switch-off logs it and the row is refused on use.
|
|
366
|
+
if (deleted.length < managed.length) {
|
|
367
|
+
throw new SecretOAuthError(`${managed.length - deleted.length} managed sign-in(s) kept changing while being revoked and were not removed; try again`);
|
|
368
|
+
}
|
|
369
|
+
return deleted.length;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* The same removal inside a caller's transaction (an account erasure, just
|
|
373
|
+
* before the user row goes): the rows are LOCKED for the transaction, so a
|
|
374
|
+
* handoff or a refresh arriving now waits and then finds no row, which is
|
|
375
|
+
* what makes the revoke final. No guard is needed under the lock; the
|
|
376
|
+
* revoke stays bounded (see {@link revokeRow}), so the transaction never
|
|
377
|
+
* waits on a provider longer than that.
|
|
378
|
+
*/
|
|
379
|
+
async removeManagedRowsLocked(userId, tx) {
|
|
380
|
+
const rows = await tx
|
|
381
|
+
.select()
|
|
382
|
+
.from(secrets)
|
|
383
|
+
.where(and(eq(secrets.userId, userId), eq(secrets.kind, 'oauth')))
|
|
384
|
+
.for('update');
|
|
385
|
+
const managed = rows.filter((r) => this.managedRefOf(r.oauthMeta) !== null);
|
|
386
|
+
if (managed.length === 0)
|
|
387
|
+
return 0;
|
|
388
|
+
await Promise.all(managed.map((row) => this.revokeRow(row)));
|
|
389
|
+
await tx.delete(secrets).where(and(eq(secrets.userId, userId), inArray(secrets.id, managed.map((r) => r.id))));
|
|
390
|
+
this.notifyMutation(userId);
|
|
391
|
+
return managed.length;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Replace what `where` names with `write`, a managed sign-in's tokens told
|
|
395
|
+
* to their provider first (see {@link revokeRow}; best effort, never holding
|
|
396
|
+
* the user's decision). The write is guarded on the ciphertext the revoke
|
|
397
|
+
* read (`setWhere`): a refresh or a handoff landing in between has rotated
|
|
398
|
+
* the tokens, and writing over THOSE unrevoked is the leak the revoke exists
|
|
399
|
+
* to prevent — as the guarded delete of {@link revokeThenDelete}. A miss
|
|
400
|
+
* re-reads and goes again, bounded; a row that kept changing is left as it
|
|
401
|
+
* is and the caller is told. With no revoker wired there is nothing to read
|
|
402
|
+
* first: one plain write, as every replacement was before managed sign-ins.
|
|
403
|
+
*/
|
|
404
|
+
async revokedReplacement(where, write) {
|
|
405
|
+
if (!this.managedRevoker)
|
|
406
|
+
return (await write())[0];
|
|
407
|
+
for (let attempt = 0; attempt < REPLACE_ATTEMPTS; attempt++) {
|
|
408
|
+
const rows = await this.db.select().from(secrets).where(where);
|
|
409
|
+
await Promise.all(rows.map((row) => this.revokeRow(row)));
|
|
410
|
+
// The row as it was read, or no row at all: a conflict with anything
|
|
411
|
+
// else (a row written meanwhile) is a miss, and the next pass reads it.
|
|
412
|
+
const guard = rows.length > 0
|
|
413
|
+
? inArray(secrets.valueEncrypted, rows.map((row) => row.valueEncrypted))
|
|
414
|
+
: eq(secrets.valueEncrypted, '');
|
|
415
|
+
const written = await write(guard);
|
|
416
|
+
if (written.length > 0)
|
|
417
|
+
return written[0];
|
|
418
|
+
}
|
|
419
|
+
throw new SecretOAuthError('the sign-in kept changing while being replaced; try again');
|
|
420
|
+
}
|
|
421
|
+
async revokeRow(row) {
|
|
422
|
+
if (!this.managedRevoker || row.kind !== 'oauth')
|
|
423
|
+
return;
|
|
424
|
+
const ref = this.managedRefOf(row.oauthMeta);
|
|
425
|
+
if (!ref)
|
|
426
|
+
return;
|
|
427
|
+
const tokens = this.readTokensSafe(row.valueEncrypted);
|
|
428
|
+
if (!tokens?.access_token)
|
|
429
|
+
return;
|
|
430
|
+
// Bounded: the delete that follows is the user's decision, and a revoker
|
|
431
|
+
// that never settles must not hold it.
|
|
432
|
+
try {
|
|
433
|
+
await bounded(this.managedRevoker.revoke(ref, { access_token: tokens.access_token, refresh_token: tokens.refresh_token }), REVOKE_TIMEOUT_MS, 'revocation');
|
|
434
|
+
}
|
|
435
|
+
catch (err) {
|
|
436
|
+
// The key and the connector name the sign-in. The error itself stays
|
|
437
|
+
// out: a revoker's message is its own, and this line is the one place a
|
|
438
|
+
// token could otherwise reach a log from this path.
|
|
439
|
+
log.warn('revoking a managed sign-in with its provider failed; deleting it here anyway', {
|
|
440
|
+
key: row.key,
|
|
441
|
+
connector: ref.connectorId,
|
|
442
|
+
failure: err instanceof Error ? err.name : typeof err,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
/** The managed reference a row's public meta carries, or null for an ordinary sign-in. */
|
|
447
|
+
managedRefOf(raw) {
|
|
448
|
+
const meta = (raw ?? {});
|
|
449
|
+
const m = meta.managed;
|
|
450
|
+
if (!m || typeof m !== 'object')
|
|
451
|
+
return null;
|
|
452
|
+
const connectorId = typeof m.connectorId === 'string' ? m.connectorId : '';
|
|
453
|
+
const scopeSet = typeof m.scopeSet === 'string' ? m.scopeSet : '';
|
|
454
|
+
return connectorId && scopeSet ? { connectorId, scopeSet } : null;
|
|
455
|
+
}
|
|
195
456
|
async countNamespace(prefix) {
|
|
196
457
|
return tally(await this.namespaceRows(prefix));
|
|
197
458
|
}
|
|
@@ -212,10 +473,10 @@ export class DbSecretsVaultService {
|
|
|
212
473
|
const rows = await this.namespaceRows(prefix);
|
|
213
474
|
if (rows.length === 0)
|
|
214
475
|
break;
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
476
|
+
// A managed sign-in under the namespace is told to its provider
|
|
477
|
+
// before the delete, for the same reason the per-row removals do —
|
|
478
|
+
// and guarded the same way, when a revoker is there to need it.
|
|
479
|
+
const deleted = await this.revokeThenDelete(inArray(secrets.id, rows.map((r) => r.id)));
|
|
219
480
|
gone.push(...deleted);
|
|
220
481
|
}
|
|
221
482
|
}
|
|
@@ -265,6 +526,7 @@ export class DbSecretsVaultService {
|
|
|
265
526
|
userId: secrets.userId,
|
|
266
527
|
kind: secrets.kind,
|
|
267
528
|
valueEncrypted: secrets.valueEncrypted,
|
|
529
|
+
oauthMeta: secrets.oauthMeta,
|
|
268
530
|
})
|
|
269
531
|
.from(secrets)
|
|
270
532
|
.where(and(inArray(secrets.key, keys), or(isNull(secrets.userId), eq(secrets.userId, userId))));
|
|
@@ -273,6 +535,15 @@ export class DbSecretsVaultService {
|
|
|
273
535
|
const adminRow = rows.find((r) => r.key === key && r.userId === null);
|
|
274
536
|
// Decrypt+parse the oauth row's token set once, then derive both fields from it.
|
|
275
537
|
const tokens = userRow?.kind === 'oauth' ? this.readTokensSafe(userRow.valueEncrypted) : undefined;
|
|
538
|
+
// The shared row holds tokens only for a managed workspace connection;
|
|
539
|
+
// an ordinary sign-in's shared row is the client secret and reads as
|
|
540
|
+
// "no tokens" here, which is the truthful answer for it.
|
|
541
|
+
const adminTokens = adminRow?.kind === 'oauth' ? this.readTokensSafe(adminRow.valueEncrypted) : undefined;
|
|
542
|
+
const adminMeta = (adminRow?.oauthMeta ?? null);
|
|
543
|
+
const accountOf = (meta) => {
|
|
544
|
+
const a = meta?.account;
|
|
545
|
+
return typeof a === 'string' && a ? a : undefined;
|
|
546
|
+
};
|
|
276
547
|
return {
|
|
277
548
|
key,
|
|
278
549
|
adminConfigured: !!adminRow,
|
|
@@ -286,9 +557,154 @@ export class DbSecretsVaultService {
|
|
|
286
557
|
// Only meaningful for an oauth row: the scopes the caller's token was granted,
|
|
287
558
|
// so a coverage check can tell whether it still covers the tool's live scopes.
|
|
288
559
|
grantedScopes: userRow?.kind === 'oauth' ? tokens?.scope : undefined,
|
|
560
|
+
adminAuthorized: adminRow?.kind === 'oauth' ? Boolean(adminTokens?.access_token) : undefined,
|
|
561
|
+
adminGrantedScopes: adminRow?.kind === 'oauth' ? adminTokens?.scope : undefined,
|
|
562
|
+
userAccount: userRow?.kind === 'oauth' ? accountOf(userRow.oauthMeta) : undefined,
|
|
563
|
+
adminAccount: adminRow?.kind === 'oauth' ? accountOf(adminMeta) : undefined,
|
|
564
|
+
// An owner's own app on the shared row: a client id without the managed mark.
|
|
565
|
+
adminBringsOwnApp: adminRow?.kind === 'oauth'
|
|
566
|
+
? this.managedRefOf(adminMeta) === null && typeof adminMeta?.clientId === 'string' && adminMeta.clientId !== ''
|
|
567
|
+
: undefined,
|
|
289
568
|
};
|
|
290
569
|
});
|
|
291
570
|
}
|
|
571
|
+
async beginManagedConnection(input) {
|
|
572
|
+
const key = this.requireKey(input.key);
|
|
573
|
+
const userId = input.userId === null ? null : this.requireUserId(input.userId);
|
|
574
|
+
const ref = input.provider.managed;
|
|
575
|
+
if (!ref)
|
|
576
|
+
throw new InvalidSecretError('a managed sign-in needs its connector reference');
|
|
577
|
+
if (typeof input.nonce !== 'string' || input.nonce.length < 16)
|
|
578
|
+
throw new InvalidSecretError('nonce is required');
|
|
579
|
+
const oauthMeta = this.publicProviderMeta(this.requireProvider(input.provider, { managed: true }));
|
|
580
|
+
const where = userId === null ? and(isNull(secrets.userId), eq(secrets.key, key)) : and(eq(secrets.userId, userId), eq(secrets.key, key));
|
|
581
|
+
// Read-modify-write on a LIVE credential: a refresh running concurrently
|
|
582
|
+
// may rotate the tokens between the read and the write, and writing our
|
|
583
|
+
// copy over them would leave the row holding a dead refresh token. So the
|
|
584
|
+
// update is guarded on the ciphertext read, and a miss re-reads and
|
|
585
|
+
// rebuilds from the fresh row; bounded, so a row that keeps changing
|
|
586
|
+
// fails loudly rather than forever. The same rule `beginOAuth` follows.
|
|
587
|
+
for (let attempt = 0; attempt < BEGIN_MANAGED_ATTEMPTS; attempt++) {
|
|
588
|
+
const [existing] = await this.db.select().from(secrets).where(where).limit(1);
|
|
589
|
+
// Keep what the SAME connection already holds (its tokens, so an
|
|
590
|
+
// abandoned reconnect costs nothing). The same connection means the same
|
|
591
|
+
// connector and scope set: a row of another connector's, or of another
|
|
592
|
+
// set's, holds a token for something else, and a declaration that
|
|
593
|
+
// changed must not hand it to the new one. Start clean over anything
|
|
594
|
+
// else too — a static value or an owner-app sign-in under this key.
|
|
595
|
+
const storedRef = existing?.kind === 'oauth' ? this.managedRefOf(existing.oauthMeta) : null;
|
|
596
|
+
const sameConnection = storedRef !== null && storedRef.connectorId === ref.connectorId && storedRef.scopeSet === ref.scopeSet;
|
|
597
|
+
let kept = {};
|
|
598
|
+
if (existing && sameConnection) {
|
|
599
|
+
try {
|
|
600
|
+
kept = this.readBlob(existing.valueEncrypted);
|
|
601
|
+
}
|
|
602
|
+
catch {
|
|
603
|
+
kept = {};
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
const next = { tokens: kept.tokens, pendingNonce: input.nonce };
|
|
607
|
+
const valueEncrypted = this.crypto().encrypt(JSON.stringify(next));
|
|
608
|
+
const account = sameConnection ? existing.oauthMeta?.account : undefined;
|
|
609
|
+
const meta = typeof account === 'string' && account ? { ...oauthMeta, account } : oauthMeta;
|
|
610
|
+
if (!existing) {
|
|
611
|
+
try {
|
|
612
|
+
await this.db.insert(secrets).values({ userId, key, kind: 'oauth', label: null, valueEncrypted, oauthMeta: meta });
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
catch (err) {
|
|
616
|
+
// Another start of the same sign-in inserted the row first: the
|
|
617
|
+
// next pass finds it and updates it, as it would any existing row.
|
|
618
|
+
if (pgErrorCode(err) === '23505')
|
|
619
|
+
continue; // unique_violation
|
|
620
|
+
throw err;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
// A row that held ANOTHER connection's tokens is about to lose them:
|
|
624
|
+
// the provider is told first, as on every other replacement.
|
|
625
|
+
if (!sameConnection && storedRef)
|
|
626
|
+
await this.revokeRow(existing);
|
|
627
|
+
const written = await this.db
|
|
628
|
+
.update(secrets)
|
|
629
|
+
.set({ kind: 'oauth', valueEncrypted, oauthMeta: meta, updatedAt: new Date() })
|
|
630
|
+
.where(and(eq(secrets.id, existing.id), eq(secrets.valueEncrypted, existing.valueEncrypted)))
|
|
631
|
+
.returning({ id: secrets.id });
|
|
632
|
+
if (Array.isArray(written) && written.length === 0)
|
|
633
|
+
continue;
|
|
634
|
+
// A row that held something ELSE (a value, another connection's
|
|
635
|
+
// tokens, an owner's app) no longer does: caches keyed on it are told.
|
|
636
|
+
// The same connection's tokens carried over unchanged tell nobody.
|
|
637
|
+
if (!kept.tokens)
|
|
638
|
+
this.notifyMutation(userId);
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
throw new SecretOAuthError('The sign-in could not be opened: its stored credential kept changing. Try again.');
|
|
642
|
+
}
|
|
643
|
+
async completeManagedConnection(input) {
|
|
644
|
+
const key = this.requireKey(input.key);
|
|
645
|
+
const userId = input.userId === null ? null : this.requireUserId(input.userId);
|
|
646
|
+
const where = userId === null ? and(isNull(secrets.userId), eq(secrets.key, key)) : and(eq(secrets.userId, userId), eq(secrets.key, key));
|
|
647
|
+
// Every refusal below is the same words: a caller that can tell "no such
|
|
648
|
+
// sign-in" from "wrong nonce" from "already used" has learned which rows
|
|
649
|
+
// exist, and the handoff route is reachable with the broker secret alone.
|
|
650
|
+
const refused = () => new SecretOAuthError('No managed sign-in is waiting for this handoff');
|
|
651
|
+
const tokens = input.tokens;
|
|
652
|
+
if (typeof tokens?.access_token !== 'string' || !tokens.access_token) {
|
|
653
|
+
throw new InvalidSecretError('the handoff carries no access token');
|
|
654
|
+
}
|
|
655
|
+
// Read, check the nonce, write guarded on what was read; and again when
|
|
656
|
+
// the write misses. A miss with the nonce still on the row is a refresh
|
|
657
|
+
// that rotated the row's tokens meanwhile (a refresh carries the nonce
|
|
658
|
+
// across, see `inFlightOf`), not a second handoff: the broker delivered
|
|
659
|
+
// these tokens once, and refusing them would lose a valid sign-in for
|
|
660
|
+
// good. A row whose nonce is gone or changed refuses on the re-read.
|
|
661
|
+
for (let attempt = 0;; attempt++) {
|
|
662
|
+
const [row] = await this.db.select().from(secrets).where(where).limit(1);
|
|
663
|
+
if (!row || row.kind !== 'oauth' || !this.managedRefOf(row.oauthMeta))
|
|
664
|
+
throw refused();
|
|
665
|
+
let blob;
|
|
666
|
+
try {
|
|
667
|
+
blob = this.readBlob(row.valueEncrypted);
|
|
668
|
+
}
|
|
669
|
+
catch {
|
|
670
|
+
throw refused();
|
|
671
|
+
}
|
|
672
|
+
if (!blob.pendingNonce || typeof input.nonce !== 'string' || !timingSafeStringEqual(input.nonce, blob.pendingNonce)) {
|
|
673
|
+
throw refused();
|
|
674
|
+
}
|
|
675
|
+
const meta = { ...(row.oauthMeta ?? {}) };
|
|
676
|
+
// A broker that echoes no `scope` delivered what the sign-in asked for:
|
|
677
|
+
// the scopes the row was opened with stand in, as `pendingScopes` does
|
|
678
|
+
// for the code exchange. Stored as unknown, the grant would count as
|
|
679
|
+
// covering nothing, and the sign-in would read as under-scoped for good.
|
|
680
|
+
// An EMPTY scope is not "none echoed": it is an empty grant, kept as such.
|
|
681
|
+
const requested = Array.isArray(meta.scopes) && meta.scopes.length > 0 ? meta.scopes.map(String).join(' ') : undefined;
|
|
682
|
+
const stored = {
|
|
683
|
+
access_token: tokens.access_token,
|
|
684
|
+
refresh_token: typeof tokens.refresh_token === 'string' ? tokens.refresh_token : undefined,
|
|
685
|
+
expires_at: typeof tokens.expires_at === 'number' ? tokens.expires_at : undefined,
|
|
686
|
+
token_type: typeof tokens.token_type === 'string' ? tokens.token_type : undefined,
|
|
687
|
+
scope: typeof tokens.scope === 'string' ? tokens.scope : requested,
|
|
688
|
+
};
|
|
689
|
+
// The nonce is one-time: it leaves with the handoff it was minted for,
|
|
690
|
+
// so the same handoff presented twice finds nothing to match.
|
|
691
|
+
const next = { tokens: stored };
|
|
692
|
+
if (typeof input.account === 'string' && input.account.trim())
|
|
693
|
+
meta.account = input.account.trim().slice(0, MAX_LABEL_LEN);
|
|
694
|
+
// Guarded on the ciphertext read above: two handoffs racing for one
|
|
695
|
+
// nonce cannot both land, whichever the provider sent first wins.
|
|
696
|
+
const landed = await this.db
|
|
697
|
+
.update(secrets)
|
|
698
|
+
.set({ valueEncrypted: this.crypto().encrypt(JSON.stringify(next)), oauthMeta: meta, updatedAt: new Date() })
|
|
699
|
+
.where(and(eq(secrets.id, row.id), eq(secrets.valueEncrypted, row.valueEncrypted)))
|
|
700
|
+
.returning({ id: secrets.id });
|
|
701
|
+
if (!(Array.isArray(landed) && landed.length === 0))
|
|
702
|
+
break;
|
|
703
|
+
if (attempt + 1 >= HANDOFF_ATTEMPTS)
|
|
704
|
+
throw refused();
|
|
705
|
+
}
|
|
706
|
+
this.notifyMutation(userId);
|
|
707
|
+
}
|
|
292
708
|
/**
|
|
293
709
|
* Decrypt+parse an oauth row's token set once, or undefined if the blob can't be
|
|
294
710
|
* read — a corrupted row or rotated encKey must degrade to "not authorized", not throw.
|
|
@@ -418,27 +834,35 @@ export class DbSecretsVaultService {
|
|
|
418
834
|
throw new InvalidSecretError('clientSecret is required');
|
|
419
835
|
}
|
|
420
836
|
// Validate the provider config the same way createOAuth does (SSRF-safe https
|
|
421
|
-
// URLs)
|
|
422
|
-
//
|
|
837
|
+
// URLs) and store it beside the secret as a record of what was saved — what
|
|
838
|
+
// `statusFor` and the setup surfaces read. A declared sign-in does not run
|
|
839
|
+
// from it: `beginToolOAuthByKey` is handed the declaration as it reads now.
|
|
423
840
|
const provider = this.requireProvider({ ...input.provider, clientSecret: input.clientSecret });
|
|
424
841
|
const blob = { clientSecret: provider.clientSecret };
|
|
425
842
|
const valueEncrypted = this.crypto().encrypt(JSON.stringify(blob));
|
|
426
843
|
const oauthMeta = this.publicProviderMeta(provider);
|
|
427
|
-
|
|
844
|
+
// An owner's own app replacing a managed workspace connection: the
|
|
845
|
+
// connection's tokens are revoked with the provider before they go.
|
|
846
|
+
await this.revokedReplacement(and(isNull(secrets.userId), eq(secrets.key, key)), (guard) => this.db
|
|
428
847
|
.insert(secrets)
|
|
429
848
|
.values({ userId: null, key, kind: 'oauth', label: null, valueEncrypted, oauthMeta })
|
|
430
849
|
.onConflictDoUpdate({
|
|
431
850
|
target: secrets.key,
|
|
432
851
|
targetWhere: isNull(secrets.userId),
|
|
433
852
|
set: { kind: 'oauth', valueEncrypted, oauthMeta, updatedAt: new Date() },
|
|
434
|
-
|
|
853
|
+
...(guard ? { setWhere: guard } : {}),
|
|
854
|
+
})
|
|
855
|
+
.returning({ id: secrets.id }));
|
|
435
856
|
this.notifyMutation(null);
|
|
436
857
|
}
|
|
437
858
|
async beginToolOAuthByKey(input) {
|
|
438
859
|
const userId = this.requireUserId(input.userId);
|
|
439
860
|
const key = this.requireKey(input.key);
|
|
440
|
-
// The shared row holds the owner-set client secret
|
|
441
|
-
//
|
|
861
|
+
// The shared row holds the owner-set client secret. For a client the
|
|
862
|
+
// platform registered itself it also holds the provider configuration, the
|
|
863
|
+
// only description of that client there is; for a declared sign-in the
|
|
864
|
+
// configuration it holds is a record of the save, and `input.provider` (the
|
|
865
|
+
// tool file as it reads now) is what the sign-in follows.
|
|
442
866
|
const [shared] = await this.db
|
|
443
867
|
.select()
|
|
444
868
|
.from(secrets)
|
|
@@ -448,11 +872,47 @@ export class DbSecretsVaultService {
|
|
|
448
872
|
throw new SecretOAuthError("This tool's owner hasn't finished setting this up");
|
|
449
873
|
}
|
|
450
874
|
const sharedBlob = this.readBlob(shared.valueEncrypted);
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
875
|
+
let meta;
|
|
876
|
+
let oauthMeta;
|
|
877
|
+
if (input.provider) {
|
|
878
|
+
// A declared client is confidential: without the owner's secret there is
|
|
879
|
+
// nothing to sign in with, whatever the stored row says of itself.
|
|
880
|
+
if (!sharedBlob.clientSecret) {
|
|
881
|
+
throw new SecretOAuthError("This tool's owner hasn't finished setting this up");
|
|
882
|
+
}
|
|
883
|
+
// The save's own validation (https, no private or local address), BEFORE
|
|
884
|
+
// the caller's row is written or a URL built: a declaration that fails it
|
|
885
|
+
// sends nothing anywhere. A MANAGED declaration names no client of its
|
|
886
|
+
// own; reaching here means an owner pinned the deployment's own app on
|
|
887
|
+
// the shared row, and that app's id is the one the flow runs with.
|
|
888
|
+
const clientId = input.provider.clientId || this.readMeta(shared.oauthMeta).clientId;
|
|
889
|
+
meta = this.requireProvider({ ...input.provider, clientId, publicClient: false });
|
|
890
|
+
oauthMeta = this.publicProviderMeta(meta);
|
|
891
|
+
// A MOVED PROPERTY, decided on the ticket (OAuth-Sign-In-Follows-The-Live-
|
|
892
|
+
// Declaration, 2026-10-27): the secret used to be bound to the token
|
|
893
|
+
// endpoint pinned at its save, so a later file edit could not send it
|
|
894
|
+
// anywhere else; now it goes where the file says at each start, so a
|
|
895
|
+
// writer of the tool file can move it (to an https, public host — the
|
|
896
|
+
// validation above still holds). Nothing refuses that; this line is the
|
|
897
|
+
// trail an operator reads when a secret went somewhere new.
|
|
898
|
+
const recorded = (shared.oauthMeta ?? {});
|
|
899
|
+
if (typeof recorded.tokenUrl === 'string' && recorded.tokenUrl !== meta.tokenUrl) {
|
|
900
|
+
// Origins only: a path or query is the file's to say, not the log's.
|
|
901
|
+
const origin = (u) => (URL.canParse(u) ? new URL(u).origin : '(unparseable)');
|
|
902
|
+
log.warn(`sign-in on ${key}: the declared token endpoint differs from the one recorded when the client secret was saved`, {
|
|
903
|
+
savedOrigin: origin(recorded.tokenUrl),
|
|
904
|
+
declaredOrigin: origin(meta.tokenUrl),
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
else {
|
|
909
|
+
meta = this.readMeta(shared.oauthMeta);
|
|
910
|
+
oauthMeta = shared.oauthMeta;
|
|
911
|
+
// A missing secret is "not set up yet" ONLY for confidential clients — a
|
|
912
|
+
// dynamically-registered PUBLIC client (PKCE-only) never has one.
|
|
913
|
+
if (!sharedBlob.clientSecret && !meta.publicClient) {
|
|
914
|
+
throw new SecretOAuthError("This tool's owner hasn't finished setting this up");
|
|
915
|
+
}
|
|
456
916
|
}
|
|
457
917
|
// Carry forward any tokens the caller already holds so re-entering the flow
|
|
458
918
|
// (or abandoning it) can't de-authorize a still-valid prior grant — only
|
|
@@ -466,10 +926,12 @@ export class DbSecretsVaultService {
|
|
|
466
926
|
// encKey must degrade to "start fresh", not crash the recovery flow — and (b)
|
|
467
927
|
// they were minted under the SAME provider config we're about to (re)write, or
|
|
468
928
|
// a refresh_token for a stale client id/secret would silently fail once carried
|
|
469
|
-
// into this row after an owner rotates the shared secret
|
|
929
|
+
// into this row after an owner rotates the shared secret or edits the
|
|
930
|
+
// declaration. Compared as values, not as serialised text: the stored side
|
|
931
|
+
// has been through jsonb, which does not keep key order, and a declared
|
|
932
|
+
// sign-in's side is built here.
|
|
470
933
|
let existingTokens;
|
|
471
|
-
if (existingUserRow?.kind === 'oauth' &&
|
|
472
|
-
JSON.stringify(existingUserRow.oauthMeta) === JSON.stringify(shared.oauthMeta)) {
|
|
934
|
+
if (existingUserRow?.kind === 'oauth' && isDeepStrictEqual(existingUserRow.oauthMeta, oauthMeta)) {
|
|
473
935
|
try {
|
|
474
936
|
existingTokens = this.readBlob(existingUserRow.valueEncrypted).tokens;
|
|
475
937
|
}
|
|
@@ -487,9 +949,13 @@ export class DbSecretsVaultService {
|
|
|
487
949
|
// that echoes no `scope` is read as granting exactly this (RFC 6749 §5.1).
|
|
488
950
|
const requestedScopes = input.scopes && input.scopes.length ? input.scopes : meta.scopes;
|
|
489
951
|
const pendingScopes = requestedScopes && requestedScopes.length ? requestedScopes.join(' ') : undefined;
|
|
490
|
-
// Provision (or reset) the caller's own oauth row for this key from the
|
|
491
|
-
//
|
|
492
|
-
//
|
|
952
|
+
// Provision (or reset) the caller's own oauth row for this key from the provider
|
|
953
|
+
// configuration this start uses + the shared secret, preserving any existing
|
|
954
|
+
// tokens. The row records that configuration because `completeOAuth` and every
|
|
955
|
+
// later refresh of this grant read it from HERE: the exchange goes to the token
|
|
956
|
+
// endpoint, under the client id, that the consent was given for. Keyed
|
|
957
|
+
// `<manual>_<VAR>` so `resolve` (scope 'user') returns the token once sign-in
|
|
958
|
+
// completes.
|
|
493
959
|
const blob = {
|
|
494
960
|
clientSecret: sharedBlob.clientSecret,
|
|
495
961
|
tokens: existingTokens,
|
|
@@ -497,17 +963,22 @@ export class DbSecretsVaultService {
|
|
|
497
963
|
pendingScopes,
|
|
498
964
|
};
|
|
499
965
|
const valueEncrypted = this.crypto().encrypt(JSON.stringify(blob));
|
|
500
|
-
|
|
966
|
+
// A managed sign-in on the caller's row (an owner pinned their own app
|
|
967
|
+
// after the person connected through the broker) is replaced by this
|
|
968
|
+
// start: its tokens are told to the provider first, and the write is
|
|
969
|
+
// guarded on what the revoke read. Tokens carried over above were minted
|
|
970
|
+
// under THIS configuration, never a managed one.
|
|
971
|
+
const row = await this.revokedReplacement(and(eq(secrets.userId, userId), eq(secrets.key, key)), (guard) => this.db
|
|
501
972
|
.insert(secrets)
|
|
502
|
-
.values({ userId, key, kind: 'oauth', label: shared.label, valueEncrypted, oauthMeta
|
|
973
|
+
.values({ userId, key, kind: 'oauth', label: shared.label, valueEncrypted, oauthMeta })
|
|
503
974
|
.onConflictDoUpdate({
|
|
504
975
|
target: [secrets.userId, secrets.key],
|
|
505
|
-
set: { kind: 'oauth', valueEncrypted, oauthMeta
|
|
976
|
+
set: { kind: 'oauth', valueEncrypted, oauthMeta, updatedAt: new Date() },
|
|
977
|
+
...(guard ? { setWhere: guard } : {}),
|
|
506
978
|
})
|
|
507
|
-
.returning();
|
|
508
|
-
// Build the consent URL exactly as beginOAuth does, from the
|
|
509
|
-
//
|
|
510
|
-
// stay owner-pinned.
|
|
979
|
+
.returning());
|
|
980
|
+
// Build the consent URL exactly as beginOAuth does, from the configuration
|
|
981
|
+
// this start uses, with the requested scopes chosen above.
|
|
511
982
|
const url = new URL(meta.authorizationUrl);
|
|
512
983
|
url.searchParams.set('response_type', 'code');
|
|
513
984
|
url.searchParams.set('client_id', meta.clientId);
|
|
@@ -583,10 +1054,16 @@ export class DbSecretsVaultService {
|
|
|
583
1054
|
return result.accessToken;
|
|
584
1055
|
}
|
|
585
1056
|
async forceRefresh(userId, key) {
|
|
1057
|
+
// The caller's own row, or — for a variable the manual declares shared
|
|
1058
|
+
// (a managed workspace connection) — the one shared row every caller
|
|
1059
|
+
// resolves. Same tier rule as `resolve`, so a 401 against a shared
|
|
1060
|
+
// token repairs the row the call actually used.
|
|
1061
|
+
const scope = (await this.scopeOf?.(key)) ?? 'admin';
|
|
1062
|
+
const scopeFilter = scope === 'user' ? eq(secrets.userId, userId) : isNull(secrets.userId);
|
|
586
1063
|
const [row] = await this.db
|
|
587
1064
|
.select()
|
|
588
1065
|
.from(secrets)
|
|
589
|
-
.where(and(
|
|
1066
|
+
.where(and(scopeFilter, eq(secrets.key, key)))
|
|
590
1067
|
.limit(1);
|
|
591
1068
|
if (!row || row.kind !== 'oauth')
|
|
592
1069
|
return 'skipped';
|
|
@@ -627,18 +1104,38 @@ export class DbSecretsVaultService {
|
|
|
627
1104
|
return this.wipeTokens(row, blob);
|
|
628
1105
|
}
|
|
629
1106
|
const meta = this.readMeta(row.oauthMeta);
|
|
630
|
-
const body = new URLSearchParams({
|
|
631
|
-
grant_type: 'refresh_token',
|
|
632
|
-
refresh_token: tokens.refresh_token,
|
|
633
|
-
client_id: meta.clientId,
|
|
634
|
-
});
|
|
635
|
-
if (blob.clientSecret)
|
|
636
|
-
body.set('client_secret', blob.clientSecret);
|
|
637
|
-
if (meta.resource)
|
|
638
|
-
body.set('resource', meta.resource);
|
|
639
1107
|
let refreshed;
|
|
640
1108
|
try {
|
|
641
|
-
|
|
1109
|
+
if (meta.managed) {
|
|
1110
|
+
// A managed sign-in renews through the broker, which holds the
|
|
1111
|
+
// client secret this deployment never sees. No broker wired is an
|
|
1112
|
+
// outage from here, not a dead grant: the stale token is served and
|
|
1113
|
+
// the next call asks again.
|
|
1114
|
+
if (!this.managedRefresher)
|
|
1115
|
+
return { outcome: 'transient' };
|
|
1116
|
+
// Bounded like a revocation: a broker that never answers is an outage
|
|
1117
|
+
// (the stale token is served) and must not hold every resolution.
|
|
1118
|
+
const delivered = await bounded(this.managedRefresher.refresh(meta.managed, tokens.refresh_token), REFRESH_TIMEOUT_MS, 'the broker refresh');
|
|
1119
|
+
refreshed = {
|
|
1120
|
+
access_token: delivered.access_token,
|
|
1121
|
+
refresh_token: delivered.refresh_token,
|
|
1122
|
+
expires_at: delivered.expires_at,
|
|
1123
|
+
token_type: delivered.token_type,
|
|
1124
|
+
scope: delivered.scope,
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
else {
|
|
1128
|
+
const body = new URLSearchParams({
|
|
1129
|
+
grant_type: 'refresh_token',
|
|
1130
|
+
refresh_token: tokens.refresh_token,
|
|
1131
|
+
client_id: meta.clientId,
|
|
1132
|
+
});
|
|
1133
|
+
if (blob.clientSecret)
|
|
1134
|
+
body.set('client_secret', blob.clientSecret);
|
|
1135
|
+
if (meta.resource)
|
|
1136
|
+
body.set('resource', meta.resource);
|
|
1137
|
+
refreshed = await this.tokenRequest(meta.tokenUrl, body);
|
|
1138
|
+
}
|
|
642
1139
|
}
|
|
643
1140
|
catch (err) {
|
|
644
1141
|
// A DEFINITIVE provider rejection (400/401 — invalid_grant and friends)
|
|
@@ -649,7 +1146,23 @@ export class DbSecretsVaultService {
|
|
|
649
1146
|
// of letting calls fail opaquely at the provider forever.
|
|
650
1147
|
if (err instanceof SecretOAuthError &&
|
|
651
1148
|
(err.providerStatus === 400 || err.providerStatus === 401)) {
|
|
652
|
-
|
|
1149
|
+
// The only trace of a sign-in that silently turns into "not connected":
|
|
1150
|
+
// say which one, the provider's reason, and what the guarded wipe did —
|
|
1151
|
+
// a concurrent refresh may have rotated the tokens first, in which case
|
|
1152
|
+
// nothing was wiped and the rotated grant is served.
|
|
1153
|
+
const result = await this.wipeTokens(row, blob);
|
|
1154
|
+
const did = result.wiped
|
|
1155
|
+
? 'sign-in wiped'
|
|
1156
|
+
: result.outcome === 'refreshed'
|
|
1157
|
+
? 'the row had been rotated by a concurrent refresh meanwhile; the rotated sign-in is kept'
|
|
1158
|
+
: 'the row had been removed or replaced meanwhile; nothing wiped';
|
|
1159
|
+
// A managed row's refusal came from the refresher, whose words are its
|
|
1160
|
+
// own (a port may put the refresh token it was handed in them): only
|
|
1161
|
+
// the status it reported is logged. The vault's own token request
|
|
1162
|
+
// writes fixed sentences, which are safe to repeat.
|
|
1163
|
+
const why = meta.managed ? `the broker refused it (HTTP ${err.providerStatus ?? 'unknown'})` : err.message;
|
|
1164
|
+
log.warn(`refresh refused for "${row.key}", ${did}: ${why}`);
|
|
1165
|
+
return result;
|
|
653
1166
|
}
|
|
654
1167
|
return { outcome: 'transient' };
|
|
655
1168
|
}
|
|
@@ -662,7 +1175,10 @@ export class DbSecretsVaultService {
|
|
|
662
1175
|
// value, empty included, is the provider's word.
|
|
663
1176
|
if (refreshed.scope === undefined)
|
|
664
1177
|
refreshed.scope = tokens.scope;
|
|
665
|
-
|
|
1178
|
+
// A sign-in in flight on this row (a managed reconnect waiting for its
|
|
1179
|
+
// handoff, a consent waiting for its exchange) survives the refresh: the
|
|
1180
|
+
// nonce or verifier it minted is what that handoff must match.
|
|
1181
|
+
const next = { clientSecret: blob.clientSecret, tokens: refreshed, ...this.inFlightOf(blob) };
|
|
666
1182
|
// Optimistic concurrency: only persist if the stored ciphertext is unchanged,
|
|
667
1183
|
// so a concurrent refresh on the same row doesn't clobber (Microsoft pattern).
|
|
668
1184
|
const stored = await this.db
|
|
@@ -708,9 +1224,23 @@ export class DbSecretsVaultService {
|
|
|
708
1224
|
return undefined;
|
|
709
1225
|
}
|
|
710
1226
|
}
|
|
711
|
-
/**
|
|
1227
|
+
/**
|
|
1228
|
+
* What a row holds for a sign-in IN FLIGHT: the one-time nonce a managed
|
|
1229
|
+
* handoff must echo, the PKCE verifier and requested scopes a code exchange
|
|
1230
|
+
* must present. A refresh or a wipe rewrites the row's tokens, never these:
|
|
1231
|
+
* dropping them would refuse the handoff or the exchange that is about to
|
|
1232
|
+
* land, for a reason the person could not see.
|
|
1233
|
+
*/
|
|
1234
|
+
inFlightOf(blob) {
|
|
1235
|
+
return {
|
|
1236
|
+
...(blob.pendingNonce ? { pendingNonce: blob.pendingNonce } : {}),
|
|
1237
|
+
...(blob.pendingVerifier ? { pendingVerifier: blob.pendingVerifier } : {}),
|
|
1238
|
+
...(blob.pendingScopes ? { pendingScopes: blob.pendingScopes } : {}),
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
/** Drop a dead grant's token set, keeping the client secret and any sign-in in flight. See {@link refreshRow}. */
|
|
712
1242
|
async wipeTokens(row, blob) {
|
|
713
|
-
const next = { clientSecret: blob.clientSecret };
|
|
1243
|
+
const next = { clientSecret: blob.clientSecret, ...this.inFlightOf(blob) };
|
|
714
1244
|
// Guarded on the ciphertext we read, so a concurrent refresh that
|
|
715
1245
|
// already persisted ROTATED tokens can't be wiped by our stale
|
|
716
1246
|
// failure. When that guard trips (0 rows), the failure was against a
|
|
@@ -722,13 +1252,17 @@ export class DbSecretsVaultService {
|
|
|
722
1252
|
.where(and(eq(secrets.id, row.id), eq(secrets.valueEncrypted, row.valueEncrypted)))
|
|
723
1253
|
.returning({ id: secrets.id });
|
|
724
1254
|
if (Array.isArray(wiped) && wiped.length === 0) {
|
|
1255
|
+
// `wiped: false` either way: this write matched nothing, and the caller
|
|
1256
|
+
// reports what it did, not what it set out to do.
|
|
725
1257
|
const fresh = await this.currentAccessToken(row.id);
|
|
726
|
-
return fresh
|
|
1258
|
+
return fresh
|
|
1259
|
+
? { outcome: 'refreshed', accessToken: fresh, wiped: false }
|
|
1260
|
+
: { outcome: 'rejected', accessToken: null, wiped: false };
|
|
727
1261
|
}
|
|
728
1262
|
// The sign-in just became not-connected — let credential-validity caches
|
|
729
1263
|
// (pooled downstream connections dialed with the dead token) drop it now.
|
|
730
1264
|
this.notifyMutation(row.userId);
|
|
731
|
-
return { outcome: 'rejected', accessToken: null };
|
|
1265
|
+
return { outcome: 'rejected', accessToken: null, wiped: true };
|
|
732
1266
|
}
|
|
733
1267
|
async putSharedOAuthProvider(input) {
|
|
734
1268
|
const key = this.requireKey(input.key);
|
|
@@ -739,7 +1273,7 @@ export class DbSecretsVaultService {
|
|
|
739
1273
|
const blob = { clientSecret: provider.clientSecret };
|
|
740
1274
|
const valueEncrypted = this.crypto().encrypt(JSON.stringify(blob));
|
|
741
1275
|
const oauthMeta = this.publicProviderMeta(provider);
|
|
742
|
-
await this.db
|
|
1276
|
+
await this.revokedReplacement(and(isNull(secrets.userId), eq(secrets.key, key)), (guard) => this.db
|
|
743
1277
|
.insert(secrets)
|
|
744
1278
|
.values({ userId: null, key, kind: 'oauth', label, valueEncrypted, oauthMeta })
|
|
745
1279
|
// Preserve an existing row's label on re-upsert (matches
|
|
@@ -749,7 +1283,9 @@ export class DbSecretsVaultService {
|
|
|
749
1283
|
target: secrets.key,
|
|
750
1284
|
targetWhere: isNull(secrets.userId),
|
|
751
1285
|
set: { kind: 'oauth', valueEncrypted, oauthMeta, updatedAt: new Date() },
|
|
752
|
-
|
|
1286
|
+
...(guard ? { setWhere: guard } : {}),
|
|
1287
|
+
})
|
|
1288
|
+
.returning({ id: secrets.id }));
|
|
753
1289
|
this.notifyMutation(null);
|
|
754
1290
|
}
|
|
755
1291
|
async getSharedOAuthProvider(key) {
|
|
@@ -797,7 +1333,15 @@ export class DbSecretsVaultService {
|
|
|
797
1333
|
throw new SecretOAuthError(msg);
|
|
798
1334
|
}
|
|
799
1335
|
if (!res.ok) {
|
|
800
|
-
|
|
1336
|
+
// The provider's error CODE rides in the message: the callback route and
|
|
1337
|
+
// the refresh path log that message, and it is the one line that says
|
|
1338
|
+
// WHY a sign-in failed (a client the provider does not accept, a refused
|
|
1339
|
+
// authorization code). Best effort: the STATUS is the contract (the
|
|
1340
|
+
// refresh path reads 400/401 off it to retire a dead grant), and a body
|
|
1341
|
+
// that fails to read must not turn a definitive refusal into an
|
|
1342
|
+
// unclassified error.
|
|
1343
|
+
const code = tokenEndpointErrorCode(await readCapped(res, TOKEN_ERROR_BODY_MAX_BYTES).catch(() => ''));
|
|
1344
|
+
throw new SecretOAuthError(describeTokenEndpointRefusal(res.status, code), res.status);
|
|
801
1345
|
}
|
|
802
1346
|
const json = (await res.json());
|
|
803
1347
|
const accessToken = typeof json.access_token === 'string' ? json.access_token : '';
|
|
@@ -835,13 +1379,17 @@ export class DbSecretsVaultService {
|
|
|
835
1379
|
const authorizationUrl = typeof meta.authorizationUrl === 'string' ? meta.authorizationUrl : '';
|
|
836
1380
|
const tokenUrl = typeof meta.tokenUrl === 'string' ? meta.tokenUrl : '';
|
|
837
1381
|
const clientId = typeof meta.clientId === 'string' ? meta.clientId : '';
|
|
838
|
-
|
|
1382
|
+
const managed = this.managedRefOf(meta) ?? undefined;
|
|
1383
|
+
// A managed row has no client id of its own (the broker's app is the
|
|
1384
|
+
// client); everything else must name one, or no flow can run from it.
|
|
1385
|
+
if (!authorizationUrl || !tokenUrl || (!clientId && !managed)) {
|
|
839
1386
|
throw new SecretOAuthError('OAuth secret is missing provider configuration');
|
|
840
1387
|
}
|
|
841
1388
|
return {
|
|
842
1389
|
authorizationUrl,
|
|
843
1390
|
tokenUrl,
|
|
844
1391
|
clientId,
|
|
1392
|
+
managed,
|
|
845
1393
|
scopes: Array.isArray(meta.scopes) ? meta.scopes.map(String) : undefined,
|
|
846
1394
|
authParams: meta.authParams && typeof meta.authParams === 'object'
|
|
847
1395
|
? meta.authParams
|
|
@@ -861,16 +1409,27 @@ export class DbSecretsVaultService {
|
|
|
861
1409
|
...(p.pkce ? { pkce: true } : {}),
|
|
862
1410
|
...(p.publicClient ? { publicClient: true } : {}),
|
|
863
1411
|
...(p.resource ? { resource: p.resource } : {}),
|
|
1412
|
+
...(p.managed ? { managed: { connectorId: p.managed.connectorId, scopeSet: p.managed.scopeSet } } : {}),
|
|
1413
|
+
...(p.account ? { account: p.account } : {}),
|
|
864
1414
|
};
|
|
865
1415
|
}
|
|
866
|
-
|
|
1416
|
+
/**
|
|
1417
|
+
* @param opts.managed Accept a MANAGED provider (no client id of its own,
|
|
1418
|
+
* a connector reference instead). Only the managed sign-in paths pass it:
|
|
1419
|
+
* everywhere else a provider needs a client id, and a `managed` field a
|
|
1420
|
+
* caller slipped in is dropped rather than making the row one.
|
|
1421
|
+
*/
|
|
1422
|
+
requireProvider(p, opts = {}) {
|
|
867
1423
|
if (!p || typeof p !== 'object')
|
|
868
1424
|
throw new InvalidSecretError('provider is required');
|
|
869
1425
|
const cfg = p;
|
|
870
1426
|
const authorizationUrl = this.requireUrl(cfg.authorizationUrl, 'authorizationUrl');
|
|
871
1427
|
const tokenUrl = this.requireUrl(cfg.tokenUrl, 'tokenUrl');
|
|
872
|
-
const
|
|
873
|
-
|
|
1428
|
+
const managed = opts.managed ? (this.managedRefOf(cfg) ?? undefined) : undefined;
|
|
1429
|
+
// A managed sign-in has no client of its own: whatever id a caller sends
|
|
1430
|
+
// beside the connector reference is not stored, so no row can claim one.
|
|
1431
|
+
const clientId = managed ? '' : typeof cfg.clientId === 'string' ? cfg.clientId.trim() : '';
|
|
1432
|
+
if (!clientId && !managed)
|
|
874
1433
|
throw new InvalidSecretError('clientId is required');
|
|
875
1434
|
const scopes = Array.isArray(cfg.scopes) ? cfg.scopes.map(String) : undefined;
|
|
876
1435
|
const authParams = cfg.authParams && typeof cfg.authParams === 'object'
|
|
@@ -890,6 +1449,8 @@ export class DbSecretsVaultService {
|
|
|
890
1449
|
resource: typeof cfg.resource === 'string' && cfg.resource.trim()
|
|
891
1450
|
? this.requireUrl(cfg.resource, 'resource')
|
|
892
1451
|
: undefined,
|
|
1452
|
+
managed,
|
|
1453
|
+
account: typeof cfg.account === 'string' && cfg.account.trim() ? cfg.account.trim().slice(0, MAX_LABEL_LEN) : undefined,
|
|
893
1454
|
};
|
|
894
1455
|
}
|
|
895
1456
|
requireUrl(value, field) {
|