@bevel-software/platform-core-backend 0.15.1 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD-PARTY-NOTICES.md +4 -4
- package/dist/core/catalog-cache-invalidation.d.ts +16 -0
- package/dist/core/catalog-cache-invalidation.d.ts.map +1 -1
- package/dist/core/catalog-cache-invalidation.js +6 -1
- package/dist/core/catalog-cache-invalidation.js.map +1 -1
- package/dist/core/catalog-revision.d.ts +39 -0
- package/dist/core/catalog-revision.d.ts.map +1 -0
- package/dist/core/catalog-revision.js +94 -0
- package/dist/core/catalog-revision.js.map +1 -0
- package/dist/core/create-core-server.d.ts +7 -0
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +221 -75
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +52 -2
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +222 -87
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +138 -0
- package/dist/core/lifecycle.d.ts.map +1 -0
- package/dist/core/lifecycle.js +218 -0
- package/dist/core/lifecycle.js.map +1 -0
- package/dist/core/public-config.d.ts +82 -0
- package/dist/core/public-config.d.ts.map +1 -0
- package/dist/core/public-config.js +78 -0
- package/dist/core/public-config.js.map +1 -0
- package/dist/core/readiness.d.ts +86 -0
- package/dist/core/readiness.d.ts.map +1 -0
- package/dist/core/readiness.js +56 -0
- package/dist/core/readiness.js.map +1 -0
- package/dist/core-config.d.ts +30 -0
- package/dist/core-config.d.ts.map +1 -1
- package/dist/core-config.js +64 -2
- package/dist/core-config.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +215 -11
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +57 -11
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +616 -155
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access-mutation.service.d.ts +71 -5
- package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
- package/dist/modules/access/access-mutation.service.js +161 -13
- package/dist/modules/access/access-mutation.service.js.map +1 -1
- package/dist/modules/access/access-view.d.ts +147 -0
- package/dist/modules/access/access-view.d.ts.map +1 -0
- package/dist/modules/access/access-view.js +211 -0
- package/dist/modules/access/access-view.js.map +1 -0
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +255 -79
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/admin-locked-commit.d.ts +2 -3
- package/dist/modules/access/admin-locked-commit.d.ts.map +1 -1
- package/dist/modules/access/admin-locked-commit.js +9 -8
- package/dist/modules/access/admin-locked-commit.js.map +1 -1
- package/dist/modules/access/admin-route-helpers.d.ts.map +1 -1
- package/dist/modules/access/admin-route-helpers.js +4 -4
- package/dist/modules/access/admin-route-helpers.js.map +1 -1
- package/dist/modules/access/change-read-gate.d.ts +53 -0
- package/dist/modules/access/change-read-gate.d.ts.map +1 -0
- package/dist/modules/access/change-read-gate.js +134 -0
- package/dist/modules/access/change-read-gate.js.map +1 -0
- package/dist/modules/access/creator-access.d.ts +33 -39
- package/dist/modules/access/creator-access.d.ts.map +1 -1
- package/dist/modules/access/creator-access.js +92 -119
- package/dist/modules/access/creator-access.js.map +1 -1
- package/dist/modules/access/directory-sync-bot.d.ts.map +1 -1
- package/dist/modules/access/directory-sync-bot.js +3 -1
- package/dist/modules/access/directory-sync-bot.js.map +1 -1
- package/dist/modules/access/groups-admin.service.d.ts +3 -3
- package/dist/modules/access/groups-admin.service.js +2 -2
- package/dist/modules/access/reference-scan.d.ts +1 -1
- package/dist/modules/access/reference-scan.d.ts.map +1 -1
- package/dist/modules/access/reference-scan.js +5 -4
- package/dist/modules/access/reference-scan.js.map +1 -1
- package/dist/modules/access/roles-admin.service.d.ts +72 -13
- package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
- package/dist/modules/access/roles-admin.service.js +71 -13
- package/dist/modules/access/roles-admin.service.js.map +1 -1
- package/dist/modules/access/synced-groups-committer.d.ts.map +1 -1
- package/dist/modules/access/synced-groups-committer.js +8 -6
- package/dist/modules/access/synced-groups-committer.js.map +1 -1
- package/dist/modules/access/synced-groups-writer.d.ts.map +1 -1
- package/dist/modules/access/synced-groups-writer.js +3 -8
- package/dist/modules/access/synced-groups-writer.js.map +1 -1
- package/dist/modules/access/user-access-removal.service.d.ts +209 -0
- package/dist/modules/access/user-access-removal.service.d.ts.map +1 -0
- package/dist/modules/access/user-access-removal.service.js +662 -0
- package/dist/modules/access/user-access-removal.service.js.map +1 -0
- package/dist/modules/access-model/access-errors.d.ts +32 -1
- package/dist/modules/access-model/access-errors.d.ts.map +1 -1
- package/dist/modules/access-model/access-errors.js +38 -11
- package/dist/modules/access-model/access-errors.js.map +1 -1
- package/dist/modules/access-model/access-grammar.d.ts +49 -24
- package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
- package/dist/modules/access-model/access-grammar.js +68 -48
- package/dist/modules/access-model/access-grammar.js.map +1 -1
- package/dist/modules/access-model/access-splice.d.ts.map +1 -1
- package/dist/modules/access-model/access-splice.js +18 -18
- package/dist/modules/access-model/access-splice.js.map +1 -1
- package/dist/modules/access-model/access-template.d.ts +22 -0
- package/dist/modules/access-model/access-template.d.ts.map +1 -0
- package/dist/modules/access-model/access-template.js +42 -0
- package/dist/modules/access-model/access-template.js.map +1 -0
- package/dist/modules/access-model/change-gate.d.ts +65 -0
- package/dist/modules/access-model/change-gate.d.ts.map +1 -0
- package/dist/modules/access-model/change-gate.js +25 -0
- package/dist/modules/access-model/change-gate.js.map +1 -0
- package/dist/modules/access-model/creator.d.ts +13 -16
- package/dist/modules/access-model/creator.d.ts.map +1 -1
- package/dist/modules/access-model/creator.js.map +1 -1
- package/dist/modules/access-model/frontmatter-lines.d.ts +27 -0
- package/dist/modules/access-model/frontmatter-lines.d.ts.map +1 -0
- package/dist/modules/access-model/frontmatter-lines.js +74 -0
- package/dist/modules/access-model/frontmatter-lines.js.map +1 -0
- package/dist/modules/access-model/plugin-principals.d.ts +5 -3
- package/dist/modules/access-model/plugin-principals.d.ts.map +1 -1
- package/dist/modules/access-model/plugin-principals.js +11 -17
- package/dist/modules/access-model/plugin-principals.js.map +1 -1
- package/dist/modules/access-model/roles-yaml-guard.d.ts +73 -2
- package/dist/modules/access-model/roles-yaml-guard.d.ts.map +1 -1
- package/dist/modules/access-model/roles-yaml-guard.js +153 -3
- package/dist/modules/access-model/roles-yaml-guard.js.map +1 -1
- package/dist/modules/admin/admin-access.service.d.ts.map +1 -1
- package/dist/modules/admin/admin-access.service.js +5 -2
- package/dist/modules/admin/admin-access.service.js.map +1 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +21 -0
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -0
- package/dist/modules/agent-instructions/agent-instructions.routes.js +38 -0
- package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -0
- package/dist/modules/agent-instructions/compose.d.ts +71 -0
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -0
- package/dist/modules/agent-instructions/compose.js +234 -0
- package/dist/modules/agent-instructions/compose.js.map +1 -0
- package/dist/modules/agent-instructions/index.d.ts +4 -0
- package/dist/modules/agent-instructions/index.d.ts.map +1 -0
- package/dist/modules/agent-instructions/index.js +4 -0
- package/dist/modules/agent-instructions/index.js.map +1 -0
- package/dist/modules/agent-instructions/read-preamble.d.ts +37 -0
- package/dist/modules/agent-instructions/read-preamble.d.ts.map +1 -0
- package/dist/modules/agent-instructions/read-preamble.js +81 -0
- package/dist/modules/agent-instructions/read-preamble.js.map +1 -0
- package/dist/modules/audit/agent-audit.service.d.ts +54 -0
- package/dist/modules/audit/agent-audit.service.d.ts.map +1 -0
- package/dist/modules/audit/agent-audit.service.js +290 -0
- package/dist/modules/audit/agent-audit.service.js.map +1 -0
- package/dist/modules/audit/agent-rest-audit.middleware.d.ts +40 -0
- package/dist/modules/audit/agent-rest-audit.middleware.d.ts.map +1 -0
- package/dist/modules/audit/agent-rest-audit.middleware.js +75 -0
- package/dist/modules/audit/agent-rest-audit.middleware.js.map +1 -0
- package/dist/modules/audit/audit.contract.d.ts +145 -0
- package/dist/modules/audit/audit.contract.d.ts.map +1 -0
- package/dist/modules/audit/audit.contract.js +35 -0
- package/dist/modules/audit/audit.contract.js.map +1 -0
- package/dist/modules/audit/audit.routes.d.ts +21 -0
- package/dist/modules/audit/audit.routes.d.ts.map +1 -0
- package/dist/modules/audit/audit.routes.js +157 -0
- package/dist/modules/audit/audit.routes.js.map +1 -0
- package/dist/modules/audit/event-classifier.d.ts +52 -0
- package/dist/modules/audit/event-classifier.d.ts.map +1 -0
- package/dist/modules/audit/event-classifier.js +123 -0
- package/dist/modules/audit/event-classifier.js.map +1 -0
- package/dist/modules/audit/index.d.ts +7 -0
- package/dist/modules/audit/index.d.ts.map +1 -0
- package/dist/modules/audit/index.js +7 -0
- package/dist/modules/audit/index.js.map +1 -0
- package/dist/modules/audit/request-audit.d.ts +57 -0
- package/dist/modules/audit/request-audit.d.ts.map +1 -0
- package/dist/modules/audit/request-audit.js +117 -0
- package/dist/modules/audit/request-audit.js.map +1 -0
- package/dist/modules/auth/account-erasure.service.d.ts +30 -4
- package/dist/modules/auth/account-erasure.service.d.ts.map +1 -1
- package/dist/modules/auth/account-erasure.service.js +96 -10
- package/dist/modules/auth/account-erasure.service.js.map +1 -1
- package/dist/modules/auth/account.routes.d.ts +6 -3
- package/dist/modules/auth/account.routes.d.ts.map +1 -1
- package/dist/modules/auth/account.routes.js +109 -10
- package/dist/modules/auth/account.routes.js.map +1 -1
- package/dist/modules/auth/auth.routes.d.ts +10 -3
- package/dist/modules/auth/auth.routes.d.ts.map +1 -1
- package/dist/modules/auth/auth.routes.js +9 -4
- package/dist/modules/auth/auth.routes.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +89 -5
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +160 -25
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.d.ts +46 -3
- package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.js +99 -21
- package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.js +14 -2
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.d.ts.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.js +56 -7
- package/dist/modules/connection-probe/connection-probe.service.js.map +1 -1
- package/dist/modules/database/advisory-lock.d.ts +103 -0
- package/dist/modules/database/advisory-lock.d.ts.map +1 -0
- package/dist/modules/database/advisory-lock.js +320 -0
- package/dist/modules/database/advisory-lock.js.map +1 -0
- package/dist/modules/database/connection.d.ts.map +1 -1
- package/dist/modules/database/connection.js +9 -1
- package/dist/modules/database/connection.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +859 -99
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +218 -0
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/database/migrate.d.ts.map +1 -1
- package/dist/modules/database/migrate.js +26 -6
- package/dist/modules/database/migrate.js.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.d.ts.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.js +4 -2
- package/dist/modules/declared-variables/declared-variables.routes.js.map +1 -1
- package/dist/modules/diff/diff.routes.d.ts.map +1 -1
- package/dist/modules/diff/diff.routes.js +2 -4
- package/dist/modules/diff/diff.routes.js.map +1 -1
- package/dist/modules/diff/diff.service.d.ts +4 -1
- package/dist/modules/diff/diff.service.d.ts.map +1 -1
- package/dist/modules/diff/diff.service.js +84 -81
- package/dist/modules/diff/diff.service.js.map +1 -1
- package/dist/modules/{workspace → kb-fs}/bevel-ignore.d.ts +14 -9
- package/dist/modules/kb-fs/bevel-ignore.d.ts.map +1 -0
- package/dist/modules/{workspace → kb-fs}/bevel-ignore.js +24 -14
- package/dist/modules/kb-fs/bevel-ignore.js.map +1 -0
- package/dist/modules/kb-fs/file-change-notifier.d.ts.map +1 -1
- package/dist/modules/kb-fs/file-change-notifier.js +3 -1
- package/dist/modules/kb-fs/file-change-notifier.js.map +1 -1
- package/dist/modules/kb-fs/git-guarded-filesystem.d.ts +45 -0
- package/dist/modules/kb-fs/git-guarded-filesystem.d.ts.map +1 -0
- package/dist/modules/kb-fs/git-guarded-filesystem.js +117 -0
- package/dist/modules/kb-fs/git-guarded-filesystem.js.map +1 -0
- package/dist/modules/kb-fs/locking-filesystem.d.ts +113 -18
- package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.js +353 -76
- package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
- package/dist/modules/kb-fs/node-fs.d.ts +23 -0
- package/dist/modules/kb-fs/node-fs.d.ts.map +1 -0
- package/dist/modules/kb-fs/node-fs.js +158 -0
- package/dist/modules/kb-fs/node-fs.js.map +1 -0
- package/dist/modules/kb-fs/read-only-filesystem.d.ts +10 -9
- package/dist/modules/kb-fs/read-only-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/read-only-filesystem.js +18 -9
- package/dist/modules/kb-fs/read-only-filesystem.js.map +1 -1
- package/dist/modules/kb-fs/repo-path.d.ts +100 -3
- package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
- package/dist/modules/kb-fs/repo-path.js +195 -3
- package/dist/modules/kb-fs/repo-path.js.map +1 -1
- package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.routes.js +4 -2
- package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -1
- package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.service.js +7 -5
- package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
- package/dist/modules/marketplace/git-http.routes.d.ts.map +1 -1
- package/dist/modules/marketplace/git-http.routes.js +6 -4
- package/dist/modules/marketplace/git-http.routes.js.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts +12 -2
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js +32 -3
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts +27 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js +45 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade.routes.d.ts.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade.routes.js +30 -6
- package/dist/modules/marketplace/github-facade/github-facade.routes.js.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade.service.d.ts +21 -9
- package/dist/modules/marketplace/github-facade/github-facade.service.d.ts.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade.service.js +60 -19
- package/dist/modules/marketplace/github-facade/github-facade.service.js.map +1 -1
- package/dist/modules/marketplace/github-facade/index.d.ts +1 -1
- package/dist/modules/marketplace/github-facade/index.d.ts.map +1 -1
- package/dist/modules/marketplace/github-facade/index.js +1 -1
- package/dist/modules/marketplace/github-facade/index.js.map +1 -1
- package/dist/modules/marketplace/marketplace-repo.service.d.ts +14 -1
- package/dist/modules/marketplace/marketplace-repo.service.d.ts.map +1 -1
- package/dist/modules/marketplace/marketplace-repo.service.js +26 -8
- package/dist/modules/marketplace/marketplace-repo.service.js.map +1 -1
- package/dist/modules/mcp/downstream-pool.d.ts +107 -0
- package/dist/modules/mcp/downstream-pool.d.ts.map +1 -0
- package/dist/modules/mcp/downstream-pool.js +221 -0
- package/dist/modules/mcp/downstream-pool.js.map +1 -0
- package/dist/modules/mcp/downstream-token-refresh.d.ts +77 -0
- package/dist/modules/mcp/downstream-token-refresh.d.ts.map +1 -0
- package/dist/modules/mcp/downstream-token-refresh.js +166 -0
- package/dist/modules/mcp/downstream-token-refresh.js.map +1 -0
- package/dist/modules/mcp/first-call-probe.cli-args.d.ts +78 -0
- package/dist/modules/mcp/first-call-probe.cli-args.d.ts.map +1 -0
- package/dist/modules/mcp/first-call-probe.cli-args.js +151 -0
- package/dist/modules/mcp/first-call-probe.cli-args.js.map +1 -0
- package/dist/modules/mcp/first-call-probe.cli.d.ts +2 -0
- package/dist/modules/mcp/first-call-probe.cli.d.ts.map +1 -0
- package/dist/modules/mcp/first-call-probe.cli.js +62 -0
- package/dist/modules/mcp/first-call-probe.cli.js.map +1 -0
- package/dist/modules/mcp/first-call-probe.d.ts +165 -0
- package/dist/modules/mcp/first-call-probe.d.ts.map +1 -0
- package/dist/modules/mcp/first-call-probe.js +298 -0
- package/dist/modules/mcp/first-call-probe.js.map +1 -0
- package/dist/modules/mcp/manual-failure-memo.d.ts +42 -17
- package/dist/modules/mcp/manual-failure-memo.d.ts.map +1 -1
- package/dist/modules/mcp/manual-failure-memo.js +79 -23
- package/dist/modules/mcp/manual-failure-memo.js.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.d.ts +10 -5
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +56 -16
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/mcp.routes.d.ts +4 -4
- package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.routes.js +91 -180
- package/dist/modules/mcp/mcp.routes.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +226 -61
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +758 -161
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +59 -0
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +184 -10
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js +3 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
- package/dist/modules/mcp/surface-log-throttle.d.ts +59 -0
- package/dist/modules/mcp/surface-log-throttle.d.ts.map +1 -0
- package/dist/modules/mcp/surface-log-throttle.js +94 -0
- package/dist/modules/mcp/surface-log-throttle.js.map +1 -0
- package/dist/modules/plugins/compile/compile-marketplace.d.ts +13 -6
- package/dist/modules/plugins/compile/compile-marketplace.d.ts.map +1 -1
- package/dist/modules/plugins/compile/compile-marketplace.js +119 -36
- package/dist/modules/plugins/compile/compile-marketplace.js.map +1 -1
- package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts +8 -1
- package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts.map +1 -1
- package/dist/modules/plugins/compile/marketplace-compiler.service.js +9 -6
- package/dist/modules/plugins/compile/marketplace-compiler.service.js.map +1 -1
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.d.ts.map +1 -1
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js +32 -9
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js.map +1 -1
- package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts +26 -6
- package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts.map +1 -1
- package/dist/modules/plugins/discovery/bundle-dialect/registry.js +71 -14
- package/dist/modules/plugins/discovery/bundle-dialect/registry.js.map +1 -1
- package/dist/modules/plugins/discovery/kb-plugin-source.d.ts +4 -2
- package/dist/modules/plugins/discovery/kb-plugin-source.d.ts.map +1 -1
- package/dist/modules/plugins/discovery/kb-plugin-source.js +9 -6
- package/dist/modules/plugins/discovery/kb-plugin-source.js.map +1 -1
- package/dist/modules/plugins/discovery/native.source.d.ts +2 -1
- package/dist/modules/plugins/discovery/native.source.d.ts.map +1 -1
- package/dist/modules/plugins/discovery/native.source.js +22 -4
- package/dist/modules/plugins/discovery/native.source.js.map +1 -1
- package/dist/modules/plugins/discovery/plugin-source.d.ts +17 -3
- package/dist/modules/plugins/discovery/plugin-source.d.ts.map +1 -1
- package/dist/modules/plugins/index.d.ts +6 -3
- package/dist/modules/plugins/index.d.ts.map +1 -1
- package/dist/modules/plugins/index.js +6 -3
- package/dist/modules/plugins/index.js.map +1 -1
- package/dist/modules/plugins/join-request-jobs.service.d.ts +250 -0
- package/dist/modules/plugins/join-request-jobs.service.d.ts.map +1 -0
- package/dist/modules/plugins/join-request-jobs.service.js +602 -0
- package/dist/modules/plugins/join-request-jobs.service.js.map +1 -0
- package/dist/modules/plugins/join-request-records.store.d.ts +161 -0
- package/dist/modules/plugins/join-request-records.store.d.ts.map +1 -0
- package/dist/modules/plugins/join-request-records.store.js +179 -0
- package/dist/modules/plugins/join-request-records.store.js.map +1 -0
- package/dist/modules/plugins/join-requests.service.d.ts.map +1 -1
- package/dist/modules/plugins/join-requests.service.js +4 -2
- package/dist/modules/plugins/join-requests.service.js.map +1 -1
- package/dist/modules/plugins/plugin-links.d.ts +4 -1
- package/dist/modules/plugins/plugin-links.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-links.js +6 -3
- package/dist/modules/plugins/plugin-links.js.map +1 -1
- package/dist/modules/plugins/plugin-links.service.d.ts +2 -5
- package/dist/modules/plugins/plugin-links.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-links.service.js +4 -13
- package/dist/modules/plugins/plugin-links.service.js.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.d.ts +91 -19
- package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.js +183 -38
- package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
- package/dist/modules/plugins/plugin-rename.service.d.ts +3 -1
- package/dist/modules/plugins/plugin-rename.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-rename.service.js +20 -11
- package/dist/modules/plugins/plugin-rename.service.js.map +1 -1
- package/dist/modules/plugins/plugins.contract.d.ts +52 -4
- package/dist/modules/plugins/plugins.contract.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +38 -5
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +190 -116
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/plugins/plugins.service.d.ts +38 -6
- package/dist/modules/plugins/plugins.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.service.js +86 -17
- package/dist/modules/plugins/plugins.service.js.map +1 -1
- package/dist/modules/plugins/plugins.tools.d.ts +25 -0
- package/dist/modules/plugins/plugins.tools.d.ts.map +1 -0
- package/dist/modules/plugins/plugins.tools.js +82 -0
- package/dist/modules/plugins/plugins.tools.js.map +1 -0
- package/dist/modules/plugins/teams.routes.d.ts +42 -0
- package/dist/modules/plugins/teams.routes.d.ts.map +1 -0
- package/dist/modules/plugins/teams.routes.js +79 -0
- package/dist/modules/plugins/teams.routes.js.map +1 -0
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +40 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.js +202 -37
- package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
- package/dist/modules/secrets-vault/index.d.ts +1 -1
- package/dist/modules/secrets-vault/index.d.ts.map +1 -1
- package/dist/modules/secrets-vault/index.js.map +1 -1
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.d.ts.map +1 -1
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js +5 -3
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-variable-loader.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-variable-loader.js +3 -1
- package/dist/modules/secrets-vault/secrets-variable-loader.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +78 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.js +40 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.js +106 -52
- package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
- package/dist/modules/settings/connection-check.d.ts +118 -0
- package/dist/modules/settings/connection-check.d.ts.map +1 -0
- package/dist/modules/settings/connection-check.js +287 -0
- package/dist/modules/settings/connection-check.js.map +1 -0
- package/dist/modules/settings/deployment-settings.service.d.ts +162 -8
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +375 -52
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/settings/git-root-folders.d.ts +54 -0
- package/dist/modules/settings/git-root-folders.d.ts.map +1 -0
- package/dist/modules/settings/git-root-folders.js +130 -0
- package/dist/modules/settings/git-root-folders.js.map +1 -0
- package/dist/modules/settings/oidc-check.d.ts +85 -0
- package/dist/modules/settings/oidc-check.d.ts.map +1 -0
- package/dist/modules/settings/oidc-check.js +172 -0
- package/dist/modules/settings/oidc-check.js.map +1 -0
- package/dist/modules/settings/setup.routes.d.ts +31 -1
- package/dist/modules/settings/setup.routes.d.ts.map +1 -1
- package/dist/modules/settings/setup.routes.js +400 -108
- package/dist/modules/settings/setup.routes.js.map +1 -1
- package/dist/modules/skills/allowed-tools-check.d.ts +132 -0
- package/dist/modules/skills/allowed-tools-check.d.ts.map +1 -0
- package/dist/modules/skills/allowed-tools-check.js +260 -0
- package/dist/modules/skills/allowed-tools-check.js.map +1 -0
- package/dist/modules/skills/index.d.ts +3 -1
- package/dist/modules/skills/index.d.ts.map +1 -1
- package/dist/modules/skills/index.js +1 -0
- package/dist/modules/skills/index.js.map +1 -1
- package/dist/modules/skills/pending-skills.service.d.ts +6 -11
- package/dist/modules/skills/pending-skills.service.d.ts.map +1 -1
- package/dist/modules/skills/pending-skills.service.js +18 -70
- package/dist/modules/skills/pending-skills.service.js.map +1 -1
- package/dist/modules/skills/skill-access-requests.routes.d.ts.map +1 -1
- package/dist/modules/skills/skill-access-requests.routes.js +9 -6
- package/dist/modules/skills/skill-access-requests.routes.js.map +1 -1
- package/dist/modules/skills/skills.contract.d.ts +44 -10
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.d.ts +8 -1
- package/dist/modules/skills/skills.routes.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.js +16 -2
- package/dist/modules/skills/skills.routes.js.map +1 -1
- package/dist/modules/skills/skills.service.d.ts +31 -7
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +191 -81
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts +4 -1
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +41 -6
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-auth/connection-key-rejection.d.ts +18 -0
- package/dist/modules/tool-auth/connection-key-rejection.d.ts.map +1 -0
- package/dist/modules/tool-auth/connection-key-rejection.js +20 -0
- package/dist/modules/tool-auth/connection-key-rejection.js.map +1 -0
- package/dist/modules/tool-auth/connection-keys-admin.routes.d.ts +14 -0
- package/dist/modules/tool-auth/connection-keys-admin.routes.d.ts.map +1 -0
- package/dist/modules/tool-auth/connection-keys-admin.routes.js +67 -0
- package/dist/modules/tool-auth/connection-keys-admin.routes.js.map +1 -0
- package/dist/modules/tool-auth/external-api-key.interface.d.ts +72 -8
- package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.d.ts +17 -6
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +83 -27
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.d.ts +15 -2
- package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.js +6 -2
- package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
- package/dist/modules/tool-auth/key-or-session.middleware.d.ts +23 -0
- package/dist/modules/tool-auth/key-or-session.middleware.d.ts.map +1 -0
- package/dist/modules/tool-auth/key-or-session.middleware.js +39 -0
- package/dist/modules/tool-auth/key-or-session.middleware.js.map +1 -0
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts +15 -1
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +17 -6
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/tool-context.d.ts +14 -0
- package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-context.js +28 -6
- package/dist/modules/tool-helpers/tool-context.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.js +22 -5
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts +6 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.js +7 -1
- package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
- package/dist/modules/tool-manuals/index.d.ts +2 -1
- package/dist/modules/tool-manuals/index.d.ts.map +1 -1
- package/dist/modules/tool-manuals/index.js +1 -0
- package/dist/modules/tool-manuals/index.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.js +8 -6
- package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts +10 -2
- package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.js +12 -15
- package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -1
- package/dist/modules/tool-manuals/pending-tools.service.d.ts +50 -0
- package/dist/modules/tool-manuals/pending-tools.service.d.ts.map +1 -0
- package/dist/modules/tool-manuals/pending-tools.service.js +149 -0
- package/dist/modules/tool-manuals/pending-tools.service.js.map +1 -0
- package/dist/modules/tool-manuals/tool-delete.service.d.ts +135 -0
- package/dist/modules/tool-manuals/tool-delete.service.d.ts.map +1 -0
- package/dist/modules/tool-manuals/tool-delete.service.js +303 -0
- package/dist/modules/tool-manuals/tool-delete.service.js.map +1 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +161 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.js +9 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +19 -2
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.js +127 -57
- package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +119 -5
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +404 -74
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +78 -6
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +88 -54
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/event-bus.d.ts +10 -4
- package/dist/modules/workflow/event-bus.d.ts.map +1 -1
- package/dist/modules/workflow/event-bus.js +14 -11
- package/dist/modules/workflow/event-bus.js.map +1 -1
- package/dist/modules/workflow/events.routes.d.ts +17 -2
- package/dist/modules/workflow/events.routes.d.ts.map +1 -1
- package/dist/modules/workflow/events.routes.js +31 -12
- package/dist/modules/workflow/events.routes.js.map +1 -1
- package/dist/modules/workflow/file-lock.service.d.ts +22 -4
- package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
- package/dist/modules/workflow/file-lock.service.js +27 -4
- package/dist/modules/workflow/file-lock.service.js.map +1 -1
- package/dist/modules/workflow/git/change-request-link.d.ts +20 -0
- package/dist/modules/workflow/git/change-request-link.d.ts.map +1 -0
- package/dist/modules/workflow/git/change-request-link.js +34 -0
- package/dist/modules/workflow/git/change-request-link.js.map +1 -0
- package/dist/modules/workflow/git/git-version.d.ts.map +1 -1
- package/dist/modules/workflow/git/git-version.js +3 -1
- package/dist/modules/workflow/git/git-version.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +263 -4
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +528 -118
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/node-git-runner.d.ts +36 -0
- package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -0
- package/dist/modules/workflow/git/node-git-runner.js +273 -0
- package/dist/modules/workflow/git/node-git-runner.js.map +1 -0
- package/dist/modules/workflow/git/pull-request.service.d.ts +51 -2
- package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.js +157 -25
- package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.service.d.ts +11 -0
- package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.service.js +24 -4
- package/dist/modules/workflow/pending-commits.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.d.ts +15 -8
- package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.js +107 -58
- package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
- package/dist/modules/workflow/recovery-bot.d.ts.map +1 -1
- package/dist/modules/workflow/recovery-bot.js +3 -1
- package/dist/modules/workflow/recovery-bot.js.map +1 -1
- package/dist/modules/workflow/review-workflow/approval-lock.d.ts +60 -0
- package/dist/modules/workflow/review-workflow/approval-lock.d.ts.map +1 -0
- package/dist/modules/workflow/review-workflow/approval-lock.js +116 -0
- package/dist/modules/workflow/review-workflow/approval-lock.js.map +1 -0
- package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts +54 -7
- package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts +64 -0
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js +269 -90
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
- package/dist/modules/workflow/sanitize-error.d.ts +5 -2
- package/dist/modules/workflow/sanitize-error.d.ts.map +1 -1
- package/dist/modules/workflow/sanitize-error.js +8 -4
- package/dist/modules/workflow/sanitize-error.js.map +1 -1
- package/dist/modules/workflow/workflow.routes.d.ts +2 -0
- package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.routes.js +259 -29
- package/dist/modules/workflow/workflow.routes.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +166 -14
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +906 -106
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/empty-dirs.d.ts +15 -0
- package/dist/modules/workspace/empty-dirs.d.ts.map +1 -0
- package/dist/modules/workspace/empty-dirs.js +90 -0
- package/dist/modules/workspace/empty-dirs.js.map +1 -0
- package/dist/modules/workspace/file-readers/content-mode.d.ts +50 -0
- package/dist/modules/workspace/file-readers/content-mode.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/content-mode.js +55 -0
- package/dist/modules/workspace/file-readers/content-mode.js.map +1 -0
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts +4 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.js +4 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.js.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.types.d.ts +5 -2
- package/dist/modules/workspace/file-readers/doc-extract.types.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.types.js +7 -4
- package/dist/modules/workspace/file-readers/doc-extract.types.js.map +1 -1
- package/dist/modules/workspace/file-readers/document-reader.d.ts +2 -0
- package/dist/modules/workspace/file-readers/document-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/document-reader.js +5 -0
- package/dist/modules/workspace/file-readers/document-reader.js.map +1 -1
- package/dist/modules/workspace/file-readers/extract-docx.d.ts +6 -0
- package/dist/modules/workspace/file-readers/extract-docx.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/extract-docx.js +6 -0
- package/dist/modules/workspace/file-readers/extract-docx.js.map +1 -1
- package/dist/modules/workspace/file-readers/file-reader.d.ts +24 -5
- package/dist/modules/workspace/file-readers/file-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/file-reader.js +11 -6
- package/dist/modules/workspace/file-readers/file-reader.js.map +1 -1
- package/dist/modules/workspace/file-readers/file-reader.registry.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/file-reader.registry.js +6 -1
- package/dist/modules/workspace/file-readers/file-reader.registry.js.map +1 -1
- package/dist/modules/workspace/file-readers/image-reader.d.ts +7 -5
- package/dist/modules/workspace/file-readers/image-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/image-reader.js +9 -5
- package/dist/modules/workspace/file-readers/image-reader.js.map +1 -1
- package/dist/modules/workspace/file-readers/ooxml-text.d.ts +3 -0
- package/dist/modules/workspace/file-readers/ooxml-text.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/ooxml-text.js +39 -2
- package/dist/modules/workspace/file-readers/ooxml-text.js.map +1 -1
- package/dist/modules/workspace/file-readers/text-reader.d.ts +29 -1
- package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/text-reader.js +57 -3
- package/dist/modules/workspace/file-readers/text-reader.js.map +1 -1
- package/dist/modules/workspace/git-internals.middleware.d.ts +28 -0
- package/dist/modules/workspace/git-internals.middleware.d.ts.map +1 -0
- package/dist/modules/workspace/git-internals.middleware.js +93 -0
- package/dist/modules/workspace/git-internals.middleware.js.map +1 -0
- package/dist/modules/workspace/not-found.d.ts +80 -0
- package/dist/modules/workspace/not-found.d.ts.map +1 -0
- package/dist/modules/workspace/not-found.js +112 -0
- package/dist/modules/workspace/not-found.js.map +1 -0
- package/dist/modules/workspace/session-ontology.gate.d.ts +1 -1
- package/dist/modules/workspace/session-ontology.gate.d.ts.map +1 -1
- package/dist/modules/workspace/session-ontology.gate.js +1 -1
- package/dist/modules/workspace/session-ontology.gate.js.map +1 -1
- package/dist/modules/workspace/startup/beside-checkout.d.ts +42 -0
- package/dist/modules/workspace/startup/beside-checkout.d.ts.map +1 -0
- package/dist/modules/workspace/startup/beside-checkout.js +110 -0
- package/dist/modules/workspace/startup/beside-checkout.js.map +1 -0
- package/dist/modules/workspace/startup/kb-git.d.ts +7 -12
- package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-git.js +34 -35
- package/dist/modules/workspace/startup/kb-git.js.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts +95 -0
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.js +257 -55
- package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts +45 -0
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js +417 -385
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/personal-spaces.step.d.ts +48 -0
- package/dist/modules/workspace/startup/steps/personal-spaces.step.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/personal-spaces.step.js +183 -0
- package/dist/modules/workspace/startup/steps/personal-spaces.step.js.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.d.ts +51 -0
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.js +158 -0
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.js.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-layout.d.ts +64 -0
- package/dist/modules/workspace/startup/steps/plugin-layout.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-layout.js +97 -0
- package/dist/modules/workspace/startup/steps/plugin-layout.js.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts +7 -1
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.js +45 -81
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts +3 -1
- package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/roles-yaml.step.js +4 -12
- package/dist/modules/workspace/startup/steps/roles-yaml.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts +6 -1
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js +154 -78
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts +96 -24
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.js +446 -138
- package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.d.ts +94 -0
- package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/template-source.js +160 -0
- package/dist/modules/workspace/startup/steps/template-source.js.map +1 -0
- package/dist/modules/workspace/workspace.routes.d.ts +14 -2
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +649 -238
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +298 -12
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +933 -269
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +42 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +1626 -146
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/modules/workspace/write-denial.d.ts +76 -0
- package/dist/modules/workspace/write-denial.d.ts.map +1 -0
- package/dist/modules/workspace/write-denial.js +138 -0
- package/dist/modules/workspace/write-denial.js.map +1 -0
- package/dist/shared/canonical-file-identity.d.ts +64 -0
- package/dist/shared/canonical-file-identity.d.ts.map +1 -0
- package/dist/shared/canonical-file-identity.js +92 -0
- package/dist/shared/canonical-file-identity.js.map +1 -0
- package/dist/shared/domain-errors.d.ts +108 -0
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +137 -0
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/email-identity.d.ts +32 -0
- package/dist/shared/email-identity.d.ts.map +1 -0
- package/dist/shared/email-identity.js +37 -0
- package/dist/shared/email-identity.js.map +1 -0
- package/dist/shared/fs.contract.d.ts +206 -0
- package/dist/shared/fs.contract.d.ts.map +1 -0
- package/dist/shared/fs.contract.js +79 -0
- package/dist/shared/fs.contract.js.map +1 -0
- package/dist/shared/git-failure.d.ts +80 -0
- package/dist/shared/git-failure.d.ts.map +1 -0
- package/dist/shared/git-failure.js +139 -0
- package/dist/shared/git-failure.js.map +1 -0
- package/dist/shared/git-internals.d.ts +23 -0
- package/dist/shared/git-internals.d.ts.map +1 -0
- package/dist/shared/git-internals.js +181 -0
- package/dist/shared/git-internals.js.map +1 -0
- package/dist/shared/git.contract.d.ts +157 -0
- package/dist/shared/git.contract.d.ts.map +1 -0
- package/dist/shared/git.contract.js +103 -0
- package/dist/shared/git.contract.js.map +1 -0
- package/dist/shared/http-errors.d.ts +33 -0
- package/dist/shared/http-errors.d.ts.map +1 -0
- package/dist/shared/http-errors.js +35 -0
- package/dist/shared/http-errors.js.map +1 -0
- package/dist/shared/logger.contract.d.ts +63 -0
- package/dist/shared/logger.contract.d.ts.map +1 -0
- package/dist/shared/logger.contract.js +126 -0
- package/dist/shared/logger.contract.js.map +1 -0
- package/dist/shared/logging.d.ts +31 -0
- package/dist/shared/logging.d.ts.map +1 -0
- package/dist/shared/logging.js +92 -0
- package/dist/shared/logging.js.map +1 -0
- package/dist/shared/path-containment.d.ts +21 -0
- package/dist/shared/path-containment.d.ts.map +1 -0
- package/dist/shared/path-containment.js +34 -0
- package/dist/shared/path-containment.js.map +1 -0
- package/dist/shared/pending-proposals.d.ts +71 -0
- package/dist/shared/pending-proposals.d.ts.map +1 -0
- package/dist/shared/pending-proposals.js +98 -0
- package/dist/shared/pending-proposals.js.map +1 -0
- package/dist/shared/printable.d.ts +22 -0
- package/dist/shared/printable.d.ts.map +1 -0
- package/dist/shared/printable.js +65 -0
- package/dist/shared/printable.js.map +1 -0
- package/dist/shared/redact-secret.d.ts +30 -0
- package/dist/shared/redact-secret.d.ts.map +1 -0
- package/dist/shared/redact-secret.js +114 -0
- package/dist/shared/redact-secret.js.map +1 -0
- package/dist/shared/rename-no-replace.d.ts +119 -0
- package/dist/shared/rename-no-replace.d.ts.map +1 -0
- package/dist/shared/rename-no-replace.js +323 -0
- package/dist/shared/rename-no-replace.js.map +1 -0
- package/dist/shared/ssrf.d.ts.map +1 -1
- package/dist/shared/ssrf.js +49 -18
- package/dist/shared/ssrf.js.map +1 -1
- package/dist/version.d.ts +10 -6
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +10 -6
- package/dist/version.js.map +1 -1
- package/kb-template/.bevelignore +4 -3
- package/kb-template/AGENTS.md +281 -36
- package/kb-template/KnowledgeBase/How to get started.md +1 -1
- package/kb-template/access.md +5 -3
- package/kb-template/mcp-description.md +27 -0
- package/migrations/0006_api_tokens_revoked_by.sql +1 -0
- package/migrations/0007_github_facade_registered.sql +1 -0
- package/migrations/0008_change_request_apply_failure.sql +4 -0
- package/migrations/0009_change_request_apply_failure_kind.sql +13 -0
- package/migrations/0010_plugin_join_requests.sql +15 -0
- package/migrations/0011_plugin_join_request_claim.sql +11 -0
- package/migrations/0012_agent_audit.sql +41 -0
- package/migrations/0013_agent_identity_and_key_soft_delete.sql +29 -0
- package/migrations/meta/0006_snapshot.json +1723 -0
- package/migrations/meta/0007_snapshot.json +1729 -0
- package/migrations/meta/0008_snapshot.json +1753 -0
- package/migrations/meta/0009_snapshot.json +1759 -0
- package/migrations/meta/0010_snapshot.json +1872 -0
- package/migrations/meta/0011_snapshot.json +1884 -0
- package/migrations/meta/0012_snapshot.json +2247 -0
- package/migrations/meta/0013_snapshot.json +2259 -0
- package/migrations/meta/_journal.json +56 -0
- package/package.json +7 -6
- package/src/__tests__/core-config.domain.test.ts +67 -2
- package/src/__tests__/kb-layout-config.test.ts +186 -1
- package/src/__tests__/open-change-gate.ts +15 -0
- package/src/core/__tests__/catalog-cache-invalidation.test.ts +83 -3
- package/src/core/__tests__/catalog-revision.test.ts +190 -0
- package/src/core/__tests__/lifecycle.test.ts +369 -0
- package/src/core/__tests__/public-config.test.ts +30 -0
- package/src/core/__tests__/readiness.test.ts +109 -0
- package/src/core/catalog-cache-invalidation.ts +20 -1
- package/src/core/catalog-revision.ts +110 -0
- package/src/core/create-core-server.ts +286 -79
- package/src/core/create-core-services.ts +315 -81
- package/src/core/lifecycle.ts +312 -0
- package/src/core/public-config.ts +86 -0
- package/src/core/readiness.ts +123 -0
- package/src/core-config.ts +70 -4
- package/src/index.ts +15 -0
- package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +362 -0
- package/src/modules/access/__tests__/access-control.atref-batch.test.ts +2 -1
- package/src/modules/access/__tests__/access-control.atref-cache.test.ts +46 -22
- package/src/modules/access/__tests__/access-control.deny-dependents.test.ts +215 -0
- package/src/modules/access/__tests__/access-control.download-read.test.ts +268 -0
- package/src/modules/access/__tests__/access-control.everyone-read.test.ts +145 -0
- package/src/modules/access/__tests__/access-control.group-read.test.ts +163 -0
- package/src/modules/access/__tests__/access-control.invalidate-race.test.ts +2 -0
- package/src/modules/access/__tests__/access-control.platform-restore.test.ts +152 -0
- package/src/modules/access/__tests__/access-control.prospective.test.ts +189 -0
- package/src/modules/access/__tests__/access-control.read-batch.test.ts +2 -1
- package/src/modules/access/__tests__/access-control.service.test.ts +123 -78
- package/src/modules/access/__tests__/access-groups.test.ts +47 -14
- package/src/modules/access/__tests__/access-md-format.test.ts +2 -1
- package/src/modules/access/__tests__/access-mutation.service.test.ts +52 -4
- package/src/modules/access/__tests__/access-own-read-grant.test.ts +2 -0
- package/src/modules/access/__tests__/access-personal-plugin.test.ts +153 -0
- package/src/modules/access/__tests__/access-plugin-principals.test.ts +18 -1
- package/src/modules/access/__tests__/access-restrict-here.test.ts +450 -0
- package/src/modules/access/__tests__/access.routes.batch.test.ts +139 -0
- package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +394 -0
- package/src/modules/access/__tests__/access.routes.group-grant.test.ts +6 -2
- package/src/modules/access/__tests__/access.routes.overrides.test.ts +3 -2
- package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +234 -0
- package/src/modules/access/__tests__/access.routes.prospective.test.ts +216 -0
- package/src/modules/access/__tests__/access.routes.revoke.test.ts +7 -2
- package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +360 -0
- package/src/modules/access/__tests__/change-read-gate.test.ts +272 -0
- package/src/modules/access/__tests__/creator-access.test.ts +81 -83
- package/src/modules/access/__tests__/grant-sources.test.ts +3 -2
- package/src/modules/access/__tests__/groups-admin.service.test.ts +6 -5
- package/src/modules/access/__tests__/roles-admin.security.test.ts +196 -0
- package/src/modules/access/__tests__/roles-admin.service.test.ts +127 -2
- package/src/modules/access/__tests__/roles-capabilities.test.ts +5 -4
- package/src/modules/access/__tests__/roles.routes.test.ts +5 -2
- package/src/modules/access/__tests__/user-access-removal.service.test.ts +402 -0
- package/src/modules/access/__tests__/users-db-double.ts +63 -0
- package/src/modules/access/access-control.interface.ts +251 -11
- package/src/modules/access/access-control.service.ts +719 -164
- package/src/modules/access/access-mutation.service.ts +182 -13
- package/src/modules/access/access-view.ts +316 -0
- package/src/modules/access/access.routes.ts +293 -95
- package/src/modules/access/admin-locked-commit.ts +12 -14
- package/src/modules/access/admin-route-helpers.ts +4 -4
- package/src/modules/access/change-read-gate.ts +148 -0
- package/src/modules/access/creator-access.ts +89 -124
- package/src/modules/access/directory-sync-bot.ts +4 -1
- package/src/modules/access/groups-admin.service.ts +3 -3
- package/src/modules/access/reference-scan.ts +5 -5
- package/src/modules/access/roles-admin.service.ts +94 -15
- package/src/modules/access/synced-groups-committer.ts +10 -15
- package/src/modules/access/synced-groups-writer.ts +3 -12
- package/src/modules/access/user-access-removal.service.ts +711 -0
- package/src/modules/access-model/__tests__/access-splice.test.ts +63 -1
- package/src/modules/access-model/__tests__/frontmatter-fences.test.ts +71 -0
- package/src/modules/access-model/__tests__/roles-yaml-guard.test.ts +131 -0
- package/src/modules/access-model/access-errors.ts +56 -15
- package/src/modules/access-model/access-grammar.ts +85 -48
- package/src/modules/access-model/access-splice.ts +18 -18
- package/src/modules/access-model/access-template.ts +44 -0
- package/src/modules/access-model/change-gate.ts +80 -0
- package/src/modules/access-model/creator.ts +18 -21
- package/src/modules/access-model/frontmatter-lines.ts +98 -0
- package/src/modules/access-model/plugin-principals.ts +12 -19
- package/src/modules/access-model/roles-yaml-guard.ts +182 -4
- package/src/modules/admin/admin-access.service.ts +6 -2
- package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +100 -0
- package/src/modules/agent-instructions/__tests__/compose.test.ts +256 -0
- package/src/modules/agent-instructions/__tests__/read-preamble.test.ts +135 -0
- package/src/modules/agent-instructions/agent-instructions.routes.ts +41 -0
- package/src/modules/agent-instructions/compose.ts +271 -0
- package/src/modules/agent-instructions/index.ts +14 -0
- package/src/modules/agent-instructions/read-preamble.ts +88 -0
- package/src/modules/audit/__tests__/agent-audit.service.test.ts +370 -0
- package/src/modules/audit/__tests__/agent-rest-audit.middleware.test.ts +113 -0
- package/src/modules/audit/__tests__/audit.routes.test.ts +201 -0
- package/src/modules/audit/__tests__/event-classifier.test.ts +121 -0
- package/src/modules/audit/agent-audit.service.ts +316 -0
- package/src/modules/audit/agent-rest-audit.middleware.ts +83 -0
- package/src/modules/audit/audit.contract.ts +164 -0
- package/src/modules/audit/audit.routes.ts +168 -0
- package/src/modules/audit/event-classifier.ts +155 -0
- package/src/modules/audit/index.ts +14 -0
- package/src/modules/audit/request-audit.ts +141 -0
- package/src/modules/auth/__tests__/account-erasure.approval-gate.test.ts +209 -0
- package/src/modules/auth/__tests__/account.routes.test.ts +178 -2
- package/src/modules/auth/__tests__/auth.middleware.test.ts +78 -0
- package/src/modules/auth/__tests__/auth.service.test.ts +338 -5
- package/src/modules/auth/__tests__/oidc-auth-provider.test.ts +352 -12
- package/src/modules/auth/__tests__/onboarding-done.route.test.ts +3 -1
- package/src/modules/auth/account-erasure.service.ts +117 -10
- package/src/modules/auth/account.routes.ts +115 -14
- package/src/modules/auth/auth.routes.ts +20 -7
- package/src/modules/auth/auth.service.ts +187 -28
- package/src/modules/auth/oidc-auth-provider.ts +120 -22
- package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +109 -1
- package/src/modules/code-mode/code-mode.tool.ts +13 -2
- package/src/modules/connection-probe/__tests__/connection-probe.service.test.ts +78 -0
- package/src/modules/connection-probe/connection-probe.service.ts +59 -7
- package/src/modules/database/__tests__/advisory-lease.test.ts +152 -0
- package/src/modules/database/__tests__/advisory-lock.test.ts +147 -0
- package/src/modules/database/advisory-lock.ts +346 -0
- package/src/modules/database/connection.ts +9 -1
- package/src/modules/database/core-schema.ts +227 -0
- package/src/modules/database/migrate.ts +27 -6
- package/src/modules/declared-variables/declared-variables.routes.ts +6 -3
- package/src/modules/diff/__tests__/diff.service.seed-atomicity.test.ts +3 -0
- package/src/modules/diff/__tests__/diff.service.test.ts +3 -0
- package/src/modules/diff/diff.routes.ts +2 -4
- package/src/modules/diff/diff.service.ts +80 -79
- package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +472 -9
- package/src/modules/kb-fs/__tests__/node-fs.test.ts +409 -0
- package/src/modules/kb-fs/__tests__/repo-path.test.ts +210 -1
- package/src/modules/{workspace → kb-fs}/bevel-ignore.ts +22 -15
- package/src/modules/kb-fs/file-change-notifier.ts +4 -1
- package/src/modules/kb-fs/git-guarded-filesystem.ts +136 -0
- package/src/modules/kb-fs/locking-filesystem.ts +399 -109
- package/src/modules/kb-fs/node-fs.ts +165 -0
- package/src/modules/kb-fs/read-only-filesystem.ts +18 -9
- package/src/modules/kb-fs/repo-path.ts +213 -3
- package/src/modules/kb-sync/kb-sync.routes.ts +5 -2
- package/src/modules/kb-sync/kb-sync.service.ts +8 -5
- package/src/modules/marketplace/__tests__/github-facade.test.ts +134 -2
- package/src/modules/marketplace/__tests__/marketplace-git.test.ts +2 -1
- package/src/modules/marketplace/__tests__/marketplace-repo.head.test.ts +47 -0
- package/src/modules/marketplace/git-http.routes.ts +7 -4
- package/src/modules/marketplace/github-facade/github-facade-admin.routes.ts +35 -3
- package/src/modules/marketplace/github-facade/github-facade-credentials.service.ts +55 -0
- package/src/modules/marketplace/github-facade/github-facade.routes.ts +32 -6
- package/src/modules/marketplace/github-facade/github-facade.service.ts +80 -19
- package/src/modules/marketplace/github-facade/index.ts +1 -0
- package/src/modules/marketplace/marketplace-repo.service.ts +23 -8
- package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +183 -13
- package/src/modules/mcp/__tests__/downstream-pool.test.ts +278 -0
- package/src/modules/mcp/__tests__/downstream-token-refresh.test.ts +178 -0
- package/src/modules/mcp/__tests__/fake-downstream-mcp-server.ts +156 -0
- package/src/modules/mcp/__tests__/first-call-probe.cli-args.test.ts +178 -0
- package/src/modules/mcp/__tests__/first-call-probe.test.ts +414 -0
- package/src/modules/mcp/__tests__/manual-failure-memo.test.ts +98 -6
- package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +68 -7
- package/src/modules/mcp/__tests__/mcp-routes-harness.ts +1 -3
- package/src/modules/mcp/__tests__/mcp.e2e.test.ts +1055 -59
- package/src/modules/mcp/__tests__/mcp.routes.local-token.test.ts +24 -4
- package/src/modules/mcp/__tests__/mcp.routes.stateless.test.ts +182 -0
- package/src/modules/mcp/__tests__/mcp.service.test.ts +391 -6
- package/src/modules/mcp/__tests__/surface-log-throttle.test.ts +169 -0
- package/src/modules/mcp/downstream-pool.ts +256 -0
- package/src/modules/mcp/downstream-token-refresh.ts +170 -0
- package/src/modules/mcp/first-call-probe.cli-args.ts +159 -0
- package/src/modules/mcp/first-call-probe.cli.ts +60 -0
- package/src/modules/mcp/first-call-probe.ts +418 -0
- package/src/modules/mcp/manual-failure-memo.ts +81 -23
- package/src/modules/mcp/mcp-auth.middleware.ts +56 -16
- package/src/modules/mcp/mcp.routes.ts +386 -482
- package/src/modules/mcp/mcp.service.ts +905 -177
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +189 -8
- package/src/modules/mcp/oauth/oauth-consent.routes.ts +4 -4
- package/src/modules/mcp/surface-log-throttle.ts +120 -0
- package/src/modules/plugins/__tests__/bundle-dialect.e2e.test.ts +10 -7
- package/src/modules/plugins/__tests__/bundle-dialect.test.ts +165 -0
- package/src/modules/plugins/__tests__/compile-marketplace.test.ts +139 -10
- package/src/modules/plugins/__tests__/fake-join-request-store.ts +189 -0
- package/src/modules/plugins/__tests__/join-request-jobs.service.test.ts +608 -0
- package/src/modules/plugins/__tests__/plugin-creation-display-name.test.ts +241 -0
- package/src/modules/plugins/__tests__/plugin-index.service.test.ts +148 -7
- package/src/modules/plugins/__tests__/plugin-links.service.test.ts +10 -13
- package/src/modules/plugins/__tests__/plugin-names.test.ts +77 -0
- package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +129 -19
- package/src/modules/plugins/__tests__/plugin-rename.service.test.ts +91 -29
- package/src/modules/plugins/__tests__/plugins.routes.test.ts +370 -19
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +131 -0
- package/src/modules/plugins/__tests__/teams.routes.test.ts +248 -0
- package/src/modules/plugins/compile/compile-marketplace.ts +143 -46
- package/src/modules/plugins/compile/marketplace-compiler.service.ts +8 -7
- package/src/modules/plugins/discovery/__tests__/kb-plugin-source.test.ts +56 -20
- package/src/modules/plugins/discovery/bundle-dialect/bundle.source.ts +30 -9
- package/src/modules/plugins/discovery/bundle-dialect/registry.ts +93 -18
- package/src/modules/plugins/discovery/kb-plugin-source.ts +10 -7
- package/src/modules/plugins/discovery/native.source.ts +21 -3
- package/src/modules/plugins/discovery/plugin-source.ts +17 -3
- package/src/modules/plugins/index.ts +16 -3
- package/src/modules/plugins/join-request-jobs.service.ts +675 -0
- package/src/modules/plugins/join-request-records.store.ts +353 -0
- package/src/modules/plugins/join-requests.service.ts +7 -4
- package/src/modules/plugins/plugin-links.service.ts +4 -17
- package/src/modules/plugins/plugin-links.ts +6 -3
- package/src/modules/plugins/plugin-provision.service.ts +209 -37
- package/src/modules/plugins/plugin-rename.service.ts +21 -9
- package/src/modules/plugins/plugins.contract.ts +52 -4
- package/src/modules/plugins/plugins.routes.ts +204 -124
- package/src/modules/plugins/plugins.service.ts +92 -18
- package/src/modules/plugins/plugins.tools.ts +88 -0
- package/src/modules/plugins/teams.routes.ts +129 -0
- package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +359 -15
- package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +248 -0
- package/src/modules/secrets-vault/__tests__/namespace-secrets.test.ts +202 -0
- package/src/modules/secrets-vault/db-secrets-vault.service.ts +217 -35
- package/src/modules/secrets-vault/index.ts +1 -0
- package/src/modules/secrets-vault/mcp-oauth-discovery.service.ts +6 -3
- package/src/modules/secrets-vault/secrets-variable-loader.ts +4 -4
- package/src/modules/secrets-vault/secrets-vault.contract.ts +93 -0
- package/src/modules/secrets-vault/secrets-vault.routes.ts +99 -38
- package/src/modules/settings/__tests__/connection-check.test.ts +233 -0
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +223 -6
- package/src/modules/settings/__tests__/git-root-folders.test.ts +247 -0
- package/src/modules/settings/__tests__/oidc-check.test.ts +185 -0
- package/src/modules/settings/__tests__/setup.routes.boot-failure.test.ts +119 -0
- package/src/modules/settings/__tests__/setup.routes.layout-phase.test.ts +170 -0
- package/src/modules/settings/__tests__/setup.routes.oidc.test.ts +555 -0
- package/src/modules/settings/__tests__/setup.routes.test.ts +667 -16
- package/src/modules/settings/connection-check.ts +351 -0
- package/src/modules/settings/deployment-settings.service.ts +444 -59
- package/src/modules/settings/git-root-folders.ts +171 -0
- package/src/modules/settings/oidc-check.ts +229 -0
- package/src/modules/settings/setup.routes.ts +456 -110
- package/src/modules/skills/__tests__/allowed-tools-check.test.ts +224 -0
- package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +210 -0
- package/src/modules/skills/__tests__/pending-skills.service.test.ts +3 -3
- package/src/modules/skills/__tests__/skills.routes.warnings.test.ts +91 -0
- package/src/modules/skills/__tests__/skills.service.test.ts +223 -2
- package/src/modules/skills/allowed-tools-check.ts +352 -0
- package/src/modules/skills/index.ts +4 -0
- package/src/modules/skills/pending-skills.service.ts +21 -82
- package/src/modules/skills/skill-access-requests.routes.ts +10 -6
- package/src/modules/skills/skills.contract.ts +43 -11
- package/src/modules/skills/skills.routes.ts +16 -1
- package/src/modules/skills/skills.service.ts +214 -84
- package/src/modules/skills/skills.tools.ts +43 -5
- package/src/modules/tool-auth/__tests__/connection-key-rejection.test.ts +192 -0
- package/src/modules/tool-auth/__tests__/connection-keys-admin.routes.test.ts +116 -0
- package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +153 -8
- package/src/modules/tool-auth/__tests__/internal-token.service.test.ts +6 -0
- package/src/modules/tool-auth/connection-key-rejection.ts +25 -0
- package/src/modules/tool-auth/connection-keys-admin.routes.ts +76 -0
- package/src/modules/tool-auth/external-api-key.interface.ts +78 -8
- package/src/modules/tool-auth/external-api-key.service.ts +98 -33
- package/src/modules/tool-auth/internal-token.service.ts +16 -2
- package/src/modules/tool-auth/key-or-session.middleware.ts +45 -0
- package/src/modules/tool-auth/tool-auth.middleware.ts +33 -8
- package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +228 -0
- package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +33 -4
- package/src/modules/tool-helpers/tool-context.ts +46 -7
- package/src/modules/tool-helpers/tool-handler.ts +23 -5
- package/src/modules/tool-helpers/tool.contract.ts +5 -0
- package/src/modules/tool-manuals/__tests__/mcp-server-edit.service.test.ts +2 -1
- package/src/modules/tool-manuals/__tests__/pending-tools.service.test.ts +411 -0
- package/src/modules/tool-manuals/__tests__/tool-delete.route.test.ts +133 -0
- package/src/modules/tool-manuals/__tests__/tool-delete.service.test.ts +469 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +21 -2
- package/src/modules/tool-manuals/__tests__/tool-manuals.cli.test.ts +9 -4
- package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +6 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.invalid.routes.test.ts +254 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.invalid.test.ts +577 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +6 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.pending.route.test.ts +102 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.restart.test.ts +285 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +178 -2
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +114 -2
- package/src/modules/tool-manuals/index.ts +3 -0
- package/src/modules/tool-manuals/mcp-json-discovery.ts +13 -10
- package/src/modules/tool-manuals/mcp-server-edit.service.ts +11 -14
- package/src/modules/tool-manuals/pending-tools.service.ts +179 -0
- package/src/modules/tool-manuals/tool-delete.service.ts +401 -0
- package/src/modules/tool-manuals/tool-manuals.contract.ts +165 -0
- package/src/modules/tool-manuals/tool-manuals.routes.ts +138 -54
- package/src/modules/tool-manuals/tool-manuals.service.ts +432 -87
- package/src/modules/tool-manuals/tool-manuals.tools.ts +81 -6
- package/src/modules/workflow/__tests__/apply-failure.test.ts +1038 -0
- package/src/modules/workflow/__tests__/event-bus.test.ts +61 -9
- package/src/modules/workflow/__tests__/fake-file-lock-db.ts +199 -0
- package/src/modules/workflow/__tests__/file-lock.service.path-identity.test.ts +237 -0
- package/src/modules/workflow/__tests__/format-affected-owners.test.ts +18 -0
- package/src/modules/workflow/__tests__/merge-announces-tree.test.ts +220 -0
- package/src/modules/workflow/__tests__/pending-commits.worker.concurrency.test.ts +187 -0
- package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +2 -0
- package/src/modules/workflow/__tests__/sanitize-error.test.ts +10 -0
- package/src/modules/workflow/__tests__/workflow.routes.folder-proposals.test.ts +128 -0
- package/src/modules/workflow/__tests__/workflow.routes.fork-point-file.test.ts +149 -0
- package/src/modules/workflow/__tests__/workflow.routes.lock-path.test.ts +305 -0
- package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +2 -0
- package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +2 -0
- package/src/modules/workflow/__tests__/workflow.service.deleted-branch-sweep.test.ts +2 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +294 -25
- package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +564 -0
- package/src/modules/workflow/__tests__/workflow.service.platform-restore.test.ts +135 -0
- package/src/modules/workflow/__tests__/workflow.service.read-gate.test.ts +172 -0
- package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +14 -0
- package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +5 -0
- package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +281 -0
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +158 -7
- package/src/modules/workflow/agent-tools/workflow.tools.ts +93 -64
- package/src/modules/workflow/event-bus.ts +26 -28
- package/src/modules/workflow/events.routes.ts +56 -21
- package/src/modules/workflow/file-lock.service.ts +27 -4
- package/src/modules/workflow/git/__tests__/change-request-link.test.ts +43 -0
- package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +1 -0
- package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +238 -1
- package/src/modules/workflow/git/__tests__/git.service.forkPoint.test.ts +245 -0
- package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +35 -0
- package/src/modules/workflow/git/__tests__/git.service.mergeChangeRequest.test.ts +149 -0
- package/src/modules/workflow/git/__tests__/git.service.revertPathsAndPush.test.ts +177 -0
- package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +114 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.getPrDetail.test.ts +69 -2
- package/src/modules/workflow/git/__tests__/pull-request.service.list-fetch.test.ts +121 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.placeholder.test.ts +79 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +1 -1
- package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-cancel.test.ts +1 -1
- package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-update.test.ts +111 -0
- package/src/modules/workflow/git/change-request-link.ts +35 -0
- package/src/modules/workflow/git/git-version.ts +5 -2
- package/src/modules/workflow/git/git.service.ts +684 -144
- package/src/modules/workflow/git/node-git-runner.ts +315 -0
- package/src/modules/workflow/git/pull-request.service.ts +179 -27
- package/src/modules/workflow/pending-commits.service.ts +26 -7
- package/src/modules/workflow/pending-commits.worker.ts +114 -69
- package/src/modules/workflow/recovery-bot.ts +4 -1
- package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +180 -1
- package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +1 -1
- package/src/modules/workflow/review-workflow/__tests__/carry-approvals-forward.test.ts +520 -0
- package/src/modules/workflow/review-workflow/__tests__/erase-approver.test.ts +84 -0
- package/src/modules/workflow/review-workflow/__tests__/merge-gate.test.ts +89 -77
- package/src/modules/workflow/review-workflow/approval-lock.ts +122 -0
- package/src/modules/workflow/review-workflow/review-workflow.interface.ts +59 -7
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +309 -105
- package/src/modules/workflow/sanitize-error.ts +9 -4
- package/src/modules/workflow/workflow.routes.ts +273 -29
- package/src/modules/workflow/workflow.service.ts +1087 -171
- package/src/modules/workspace/__tests__/empty-dirs.test.ts +44 -0
- package/src/modules/workspace/__tests__/file-stat-access.test.ts +301 -0
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +883 -0
- package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +464 -0
- package/src/modules/workspace/__tests__/workspace.routes.branch-errors.test.ts +139 -0
- package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +189 -37
- package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +51 -11
- package/src/modules/workspace/__tests__/workspace.routes.download-implies-read.test.ts +161 -0
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +109 -10
- package/src/modules/workspace/__tests__/workspace.routes.file-writes.test.ts +624 -0
- package/src/modules/workspace/__tests__/workspace.routes.folders.test.ts +409 -0
- package/src/modules/workspace/__tests__/workspace.routes.move-platform-files.test.ts +327 -0
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +25 -6
- package/src/modules/workspace/__tests__/workspace.service.ensure-remotes-fetched.test.ts +109 -0
- package/src/modules/workspace/__tests__/workspace.service.list-cloned.test.ts +6 -5
- package/src/modules/workspace/__tests__/workspace.service.move-never-overwrites.test.ts +218 -0
- package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +56 -1
- package/src/modules/workspace/__tests__/workspace.service.test.ts +811 -42
- package/src/modules/workspace/__tests__/workspace.service.unknown-branch.test.ts +197 -0
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +128 -0
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +212 -0
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +2632 -110
- package/src/modules/workspace/__tests__/write-denial.test.ts +75 -0
- package/src/modules/workspace/empty-dirs.ts +79 -0
- package/src/modules/workspace/file-readers/README.md +32 -0
- package/src/modules/workspace/file-readers/__tests__/doc-extract.test.ts +59 -2
- package/src/modules/workspace/file-readers/__tests__/file-reader.registry.test.ts +133 -4
- package/src/modules/workspace/file-readers/__tests__/fixtures/soft-break.docx +0 -0
- package/src/modules/workspace/file-readers/__tests__/fixtures/tab.docx +0 -0
- package/src/modules/workspace/file-readers/content-mode.ts +87 -0
- package/src/modules/workspace/file-readers/doc-extract.service.ts +4 -1
- package/src/modules/workspace/file-readers/doc-extract.types.ts +8 -4
- package/src/modules/workspace/file-readers/document-reader.ts +6 -0
- package/src/modules/workspace/file-readers/extract-docx.ts +6 -0
- package/src/modules/workspace/file-readers/file-reader.registry.ts +9 -1
- package/src/modules/workspace/file-readers/file-reader.ts +27 -6
- package/src/modules/workspace/file-readers/image-reader.ts +10 -5
- package/src/modules/workspace/file-readers/ooxml-text.ts +38 -2
- package/src/modules/workspace/file-readers/text-reader.ts +61 -4
- package/src/modules/workspace/git-internals.middleware.ts +86 -0
- package/src/modules/workspace/not-found.ts +119 -0
- package/src/modules/workspace/session-ontology.gate.ts +1 -1
- package/src/modules/workspace/startup/__tests__/beside-checkout.test.ts +181 -0
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.degraded.test.ts +361 -0
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +53 -16
- package/src/modules/workspace/startup/beside-checkout.ts +112 -0
- package/src/modules/workspace/startup/kb-git.ts +43 -36
- package/src/modules/workspace/startup/kb-startup-runner.ts +303 -63
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +1215 -73
- package/src/modules/workspace/startup/steps/groups-to-plugins.step.ts +460 -417
- package/src/modules/workspace/startup/steps/personal-spaces.step.ts +191 -0
- package/src/modules/workspace/startup/steps/plugin-display-names.step.ts +169 -0
- package/src/modules/workspace/startup/steps/plugin-layout.ts +110 -0
- package/src/modules/workspace/startup/steps/plugin-manifests.step.ts +46 -85
- package/src/modules/workspace/startup/steps/roles-yaml.step.ts +6 -12
- package/src/modules/workspace/startup/steps/seed-tree.ts +159 -78
- package/src/modules/workspace/startup/steps/template-files.step.ts +492 -141
- package/src/modules/workspace/startup/steps/template-source.ts +168 -0
- package/src/modules/workspace/workspace.routes.ts +687 -257
- package/src/modules/workspace/workspace.service.ts +1000 -303
- package/src/modules/workspace/workspace.tools.ts +1823 -180
- package/src/modules/workspace/write-denial.ts +189 -0
- package/src/shared/__tests__/canonical-file-identity.test.ts +154 -0
- package/src/shared/__tests__/email-identity.test.ts +35 -0
- package/src/shared/__tests__/filename.test.ts +0 -0
- package/src/shared/__tests__/frontmatter-id.test.ts +5 -0
- package/src/shared/__tests__/git-failure.test.ts +198 -0
- package/src/shared/__tests__/git-internals.test.ts +156 -0
- package/src/shared/__tests__/http-errors.test.ts +42 -0
- package/src/shared/__tests__/logging.test.ts +185 -0
- package/src/shared/__tests__/path-containment.test.ts +60 -0
- package/src/shared/__tests__/pending-proposals.readAt.test.ts +102 -0
- package/src/shared/__tests__/printable.test.ts +64 -0
- package/src/shared/__tests__/redact-secret.test.ts +122 -0
- package/src/shared/__tests__/rename-no-replace.test.ts +466 -0
- package/src/shared/__tests__/ssrf.test.ts +20 -0
- package/src/shared/canonical-file-identity.ts +92 -0
- package/src/shared/domain-errors.ts +149 -0
- package/src/shared/email-identity.ts +38 -0
- package/src/shared/fs.contract.ts +225 -0
- package/src/shared/git-failure.ts +196 -0
- package/src/shared/git-internals.ts +179 -0
- package/src/shared/git.contract.ts +170 -0
- package/src/shared/http-errors.ts +36 -0
- package/src/shared/logger.contract.ts +141 -0
- package/src/shared/logging.ts +96 -0
- package/src/shared/path-containment.ts +34 -0
- package/src/shared/pending-proposals.ts +161 -0
- package/src/shared/printable.ts +68 -0
- package/src/shared/redact-secret.ts +118 -0
- package/src/shared/rename-no-replace.ts +386 -0
- package/src/shared/ssrf.ts +44 -16
- package/src/version.ts +10 -6
- package/dist/modules/access/access-errors.d.ts +0 -34
- package/dist/modules/access/access-errors.d.ts.map +0 -1
- package/dist/modules/access/access-errors.js +0 -40
- package/dist/modules/access/access-errors.js.map +0 -1
- package/dist/modules/access/access-splice.d.ts +0 -140
- package/dist/modules/access/access-splice.d.ts.map +0 -1
- package/dist/modules/access/access-splice.js +0 -389
- package/dist/modules/access/access-splice.js.map +0 -1
- package/dist/modules/access/group-files.d.ts +0 -83
- package/dist/modules/access/group-files.d.ts.map +0 -1
- package/dist/modules/access/group-files.js +0 -167
- package/dist/modules/access/group-files.js.map +0 -1
- package/dist/modules/access/kb-read-filter.d.ts +0 -41
- package/dist/modules/access/kb-read-filter.d.ts.map +0 -1
- package/dist/modules/access/kb-read-filter.js +0 -60
- package/dist/modules/access/kb-read-filter.js.map +0 -1
- package/dist/modules/access/render-roles-yaml.d.ts +0 -22
- package/dist/modules/access/render-roles-yaml.d.ts.map +0 -1
- package/dist/modules/access/render-roles-yaml.js +0 -56
- package/dist/modules/access/render-roles-yaml.js.map +0 -1
- package/dist/modules/access/roles-yaml-guard.d.ts +0 -56
- package/dist/modules/access/roles-yaml-guard.d.ts.map +0 -1
- package/dist/modules/access/roles-yaml-guard.js +0 -79
- package/dist/modules/access/roles-yaml-guard.js.map +0 -1
- package/dist/modules/diff/diff-paths.d.ts +0 -2
- package/dist/modules/diff/diff-paths.d.ts.map +0 -1
- package/dist/modules/diff/diff-paths.js +0 -9
- package/dist/modules/diff/diff-paths.js.map +0 -1
- package/dist/modules/groups/group-provision.service.d.ts +0 -130
- package/dist/modules/groups/group-provision.service.d.ts.map +0 -1
- package/dist/modules/groups/group-provision.service.js +0 -288
- package/dist/modules/groups/group-provision.service.js.map +0 -1
- package/dist/modules/groups/groups.contract.d.ts +0 -106
- package/dist/modules/groups/groups.contract.d.ts.map +0 -1
- package/dist/modules/groups/groups.contract.js +0 -36
- package/dist/modules/groups/groups.contract.js.map +0 -1
- package/dist/modules/groups/groups.routes.d.ts +0 -42
- package/dist/modules/groups/groups.routes.d.ts.map +0 -1
- package/dist/modules/groups/groups.routes.js +0 -379
- package/dist/modules/groups/groups.routes.js.map +0 -1
- package/dist/modules/groups/groups.service.d.ts +0 -60
- package/dist/modules/groups/groups.service.d.ts.map +0 -1
- package/dist/modules/groups/groups.service.js +0 -172
- package/dist/modules/groups/groups.service.js.map +0 -1
- package/dist/modules/groups/index.d.ts +0 -7
- package/dist/modules/groups/index.d.ts.map +0 -1
- package/dist/modules/groups/index.js +0 -6
- package/dist/modules/groups/index.js.map +0 -1
- package/dist/modules/groups/join-proposals.d.ts +0 -53
- package/dist/modules/groups/join-proposals.d.ts.map +0 -1
- package/dist/modules/groups/join-proposals.js +0 -67
- package/dist/modules/groups/join-proposals.js.map +0 -1
- package/dist/modules/groups/join-requests.service.d.ts +0 -81
- package/dist/modules/groups/join-requests.service.d.ts.map +0 -1
- package/dist/modules/groups/join-requests.service.js +0 -135
- package/dist/modules/groups/join-requests.service.js.map +0 -1
- package/dist/modules/mcp/mcp-session-store.d.ts +0 -74
- package/dist/modules/mcp/mcp-session-store.d.ts.map +0 -1
- package/dist/modules/mcp/mcp-session-store.js +0 -131
- package/dist/modules/mcp/mcp-session-store.js.map +0 -1
- package/dist/modules/workflow/file-change-notifier.d.ts +0 -38
- package/dist/modules/workflow/file-change-notifier.d.ts.map +0 -1
- package/dist/modules/workflow/file-change-notifier.js +0 -22
- package/dist/modules/workflow/file-change-notifier.js.map +0 -1
- package/dist/modules/workflow/git/branch-name.d.ts +0 -10
- package/dist/modules/workflow/git/branch-name.d.ts.map +0 -1
- package/dist/modules/workflow/git/branch-name.js +0 -76
- package/dist/modules/workflow/git/branch-name.js.map +0 -1
- package/dist/modules/workflow/git/clone-config.d.ts +0 -61
- package/dist/modules/workflow/git/clone-config.d.ts.map +0 -1
- package/dist/modules/workflow/git/clone-config.js +0 -69
- package/dist/modules/workflow/git/clone-config.js.map +0 -1
- package/dist/modules/workflow/git/mutex.d.ts +0 -11
- package/dist/modules/workflow/git/mutex.d.ts.map +0 -1
- package/dist/modules/workflow/git/mutex.js +0 -23
- package/dist/modules/workflow/git/mutex.js.map +0 -1
- package/dist/modules/workflow/locking-filesystem.d.ts +0 -137
- package/dist/modules/workflow/locking-filesystem.d.ts.map +0 -1
- package/dist/modules/workflow/locking-filesystem.js +0 -553
- package/dist/modules/workflow/locking-filesystem.js.map +0 -1
- package/dist/modules/workflow/read-only-filesystem.d.ts +0 -24
- package/dist/modules/workflow/read-only-filesystem.d.ts.map +0 -1
- package/dist/modules/workflow/read-only-filesystem.js +0 -39
- package/dist/modules/workflow/read-only-filesystem.js.map +0 -1
- package/dist/modules/workflow/workflow.errors.d.ts +0 -197
- package/dist/modules/workflow/workflow.errors.d.ts.map +0 -1
- package/dist/modules/workflow/workflow.errors.js +0 -298
- package/dist/modules/workflow/workflow.errors.js.map +0 -1
- package/dist/modules/workspace/bevel-ignore.d.ts.map +0 -1
- package/dist/modules/workspace/bevel-ignore.js.map +0 -1
- package/dist/modules/workspace/kb-seed.interface.d.ts +0 -36
- package/dist/modules/workspace/kb-seed.interface.d.ts.map +0 -1
- package/dist/modules/workspace/kb-seed.interface.js +0 -2
- package/dist/modules/workspace/kb-seed.interface.js.map +0 -1
- package/dist/modules/workspace/kb-seed.service.d.ts +0 -125
- package/dist/modules/workspace/kb-seed.service.d.ts.map +0 -1
- package/dist/modules/workspace/kb-seed.service.js +0 -535
- package/dist/modules/workspace/kb-seed.service.js.map +0 -1
- package/dist/modules/workspace/plugins-migration.d.ts +0 -50
- package/dist/modules/workspace/plugins-migration.d.ts.map +0 -1
- package/dist/modules/workspace/plugins-migration.js +0 -379
- package/dist/modules/workspace/plugins-migration.js.map +0 -1
- package/dist/shared/fs-errors.d.ts +0 -8
- package/dist/shared/fs-errors.d.ts.map +0 -1
- package/dist/shared/fs-errors.js +0 -11
- package/dist/shared/fs-errors.js.map +0 -1
- package/dist/shared/fs-walk.d.ts +0 -23
- package/dist/shared/fs-walk.d.ts.map +0 -1
- package/dist/shared/fs-walk.js +0 -42
- package/dist/shared/fs-walk.js.map +0 -1
- package/dist/shared/hash-email.d.ts +0 -11
- package/dist/shared/hash-email.d.ts.map +0 -1
- package/dist/shared/hash-email.js +0 -14
- package/dist/shared/hash-email.js.map +0 -1
- package/dist/shared/kb-walk.d.ts +0 -52
- package/dist/shared/kb-walk.d.ts.map +0 -1
- package/dist/shared/kb-walk.js +0 -76
- package/dist/shared/kb-walk.js.map +0 -1
- package/src/modules/diff/diff-paths.ts +0 -9
- package/src/modules/mcp/__tests__/mcp-session-store.test.ts +0 -151
- package/src/modules/mcp/__tests__/mcp.routes.session.test.ts +0 -226
- package/src/modules/mcp/mcp-session-store.ts +0 -162
- package/src/shared/__tests__/fs-walk.test.ts +0 -63
- package/src/shared/__tests__/kb-walk.test.ts +0 -107
- package/src/shared/fs-errors.ts +0 -10
- package/src/shared/fs-walk.ts +0 -45
- package/src/shared/hash-email.ts +0 -14
- package/src/shared/kb-walk.ts +0 -95
|
@@ -18,6 +18,15 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import path from 'node:path';
|
|
21
|
+
import { logger } from '../../shared/logging.js';
|
|
22
|
+
|
|
23
|
+
// One logger per tag this file has always written under, so a line's prefix
|
|
24
|
+
// is unchanged for a reader and its `module` field is exact for a filter.
|
|
25
|
+
const log = logger('workflow');
|
|
26
|
+
const syncLog = logger('sync');
|
|
27
|
+
const lockLog = logger('lock');
|
|
28
|
+
const crLog = logger('cr');
|
|
29
|
+
const mergeLog = logger('merge');
|
|
21
30
|
import { promises as fs } from 'node:fs';
|
|
22
31
|
import { execFile } from 'node:child_process';
|
|
23
32
|
import { promisify } from 'node:util';
|
|
@@ -31,20 +40,30 @@ import type {
|
|
|
31
40
|
Change,
|
|
32
41
|
ChangeInput,
|
|
33
42
|
ChangeRequest,
|
|
43
|
+
ChangeRequestApplyFailureKind,
|
|
34
44
|
ChangeRequestComment,
|
|
35
45
|
ChangeRequestDetail,
|
|
36
46
|
ChangeRequestState,
|
|
37
47
|
ChangedFile,
|
|
38
48
|
FileApproval,
|
|
39
49
|
FileLock,
|
|
50
|
+
FolderChangeRequest,
|
|
51
|
+
FolderChangeRequestRemoval,
|
|
40
52
|
IWorkflowService,
|
|
53
|
+
MergeBranchOutcome,
|
|
41
54
|
MergeChangeRequestOutcome,
|
|
42
55
|
OpenChangeRequestInput,
|
|
43
56
|
PostChangeRequestCommentInput,
|
|
44
57
|
RemoteSyncPullResult,
|
|
45
58
|
} from '@bevel-software/platform-shared';
|
|
46
|
-
import {
|
|
47
|
-
|
|
59
|
+
import {
|
|
60
|
+
isProtectedBranch,
|
|
61
|
+
DEFAULT_BRANCH,
|
|
62
|
+
isFolderPlaceholder,
|
|
63
|
+
folderPlaceholderPath,
|
|
64
|
+
isPlatformRestoreShape,
|
|
65
|
+
} from '@bevel-software/platform-shared';
|
|
66
|
+
import { and, eq, inArray, isNotNull, isNull, lt, or } from 'drizzle-orm';
|
|
48
67
|
import type { Database } from '../database/connection.js';
|
|
49
68
|
import { changeRequests } from '../database/schema.js';
|
|
50
69
|
import type { GitService } from './git/git.service.js';
|
|
@@ -54,16 +73,18 @@ import type { WorkspaceService } from '../workspace/workspace.service.js';
|
|
|
54
73
|
import { workspaceIdForBranch, branchForWorkspaceId } from '../../shared/workspace-id.js';
|
|
55
74
|
import type { IAccessControl } from '../access/access-control.interface.js';
|
|
56
75
|
import { FileLockService } from './file-lock.service.js';
|
|
76
|
+
import { canonicalFileIdentity } from '../../shared/canonical-file-identity.js';
|
|
57
77
|
import { PendingCommitsService } from './pending-commits.service.js';
|
|
58
78
|
import type { WorkflowEventBus } from './event-bus.js';
|
|
59
79
|
import { sanitizeError } from './sanitize-error.js';
|
|
60
80
|
import type { FileChangeNotifier } from '../kb-fs/file-change-notifier.js';
|
|
61
81
|
import { WorkflowHooks } from './workflow-hooks.js';
|
|
62
82
|
import { WorkspaceMutex } from '../kb-fs/mutex.js';
|
|
63
|
-
import { hashEmail } from '../../shared/
|
|
83
|
+
import { canonicalEmail, hashEmail } from '../../shared/email-identity.js';
|
|
64
84
|
import {
|
|
65
85
|
ChangeRequestConflictsError,
|
|
66
86
|
DuplicateChangeRequestError,
|
|
87
|
+
OpenChangeRequestBlocksMergeError,
|
|
67
88
|
RolesYamlPreservationError,
|
|
68
89
|
PullRebaseConflictError,
|
|
69
90
|
PushNeedsAgentResolutionError,
|
|
@@ -73,12 +94,17 @@ import {
|
|
|
73
94
|
} from '../../shared/domain-errors.js';
|
|
74
95
|
import { RECOVERY_BOT_EMAIL, RECOVERY_BOT_NAME } from './recovery-bot.js';
|
|
75
96
|
import { AccessDeniedError } from '../access-model/access-errors.js';
|
|
97
|
+
import type { IChangeReadGate } from '../access-model/change-gate.js';
|
|
98
|
+
import { printable } from '../../shared/printable.js';
|
|
76
99
|
|
|
77
100
|
const execFileAsync = promisify(execFile);
|
|
78
101
|
|
|
79
102
|
/** The partial unique index that enforces one OPEN change request per (source, target) pair. */
|
|
80
103
|
const OPEN_PAIR_CONSTRAINT = 'change_requests_open_pair_unq';
|
|
81
104
|
|
|
105
|
+
/** Cap on a persisted apply refusal — long enough to name the files a gate waits on. */
|
|
106
|
+
const APPLY_FAILURE_MAX_LEN = 1000;
|
|
107
|
+
|
|
82
108
|
/**
|
|
83
109
|
* True only for a Postgres unique violation (23505) on the open-CR-per-pair
|
|
84
110
|
* index. `change_requests` also carries `change_requests_number_unq` on its
|
|
@@ -160,7 +186,9 @@ const formatWriter = (u: { name: string; email: string }): string =>
|
|
|
160
186
|
* the overflow is collapsed into a single deduped summary of every eligible
|
|
161
187
|
* approver across the change. This keeps the body bounded regardless of how
|
|
162
188
|
* many files the change touches (see {@link MAX_AFFECTED_PATHS_LISTED}).
|
|
163
|
-
* Returns '' when no path has resolvable eligibility.
|
|
189
|
+
* Returns '' when no path has resolvable eligibility. The empty-folder
|
|
190
|
+
* placeholder is never listed or counted: it is not content, and no one
|
|
191
|
+
* reviews it.
|
|
164
192
|
*/
|
|
165
193
|
export function formatAffectedOwnersBlock(
|
|
166
194
|
paths: string[],
|
|
@@ -172,6 +200,7 @@ export function formatAffectedOwnersBlock(
|
|
|
172
200
|
const allUsers = new Map<string, string>(); // email -> display, deduped
|
|
173
201
|
let eligible = 0;
|
|
174
202
|
for (const p of paths) {
|
|
203
|
+
if (isFolderPlaceholder(p)) continue;
|
|
175
204
|
const info = resolved.get(p);
|
|
176
205
|
if (!info) continue;
|
|
177
206
|
const parts: string[] = [];
|
|
@@ -204,6 +233,28 @@ export function formatAffectedOwnersBlock(
|
|
|
204
233
|
return out.join('\n');
|
|
205
234
|
}
|
|
206
235
|
|
|
236
|
+
/** Git commit subjects are capped at 200 characters (`GitService.commitFile`). */
|
|
237
|
+
const MAX_COMMIT_SUBJECT = 200;
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* A commit subject that always fits: `full` when it does, else `short` (the
|
|
241
|
+
* file's name instead of its path, a count instead of every request), cut to
|
|
242
|
+
* the cap as a last resort. A long path or many requests on one branch must
|
|
243
|
+
* never fail the commit — and with it the whole folder removal.
|
|
244
|
+
*/
|
|
245
|
+
function commitSubject(full: string, short: string): string {
|
|
246
|
+
if (full.length <= MAX_COMMIT_SUBJECT) return full;
|
|
247
|
+
if (short.length <= MAX_COMMIT_SUBJECT) return short;
|
|
248
|
+
return `${short.slice(0, MAX_COMMIT_SUBJECT - 1)}…`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const basenameOf = (repoRelPath: string): string => repoRelPath.split('/').pop() ?? repoRelPath;
|
|
252
|
+
|
|
253
|
+
/** `Docs/Sub` and `Docs/Sub/` both → `Docs/Sub/`: a prefix that cannot match `Docs/Subway`. */
|
|
254
|
+
function folderPrefix(folder: string): string {
|
|
255
|
+
return `${folder.replace(/\/+$/, '')}/`;
|
|
256
|
+
}
|
|
257
|
+
|
|
207
258
|
export class WorkflowService implements IWorkflowService {
|
|
208
259
|
/**
|
|
209
260
|
* Concrete `PullRequestService` (not the interface) so the facade can call
|
|
@@ -241,6 +292,13 @@ export class WorkflowService implements IWorkflowService {
|
|
|
241
292
|
*/
|
|
242
293
|
private readonly pendingCommits: PendingCommitsService,
|
|
243
294
|
private readonly kbDirName: string,
|
|
295
|
+
/**
|
|
296
|
+
* The read-before-write gate `acquireLock` asks on every branch (see
|
|
297
|
+
* `access-model/change-gate.ts`). Required, and checked at construction:
|
|
298
|
+
* a service without it would let a change land where its author cannot
|
|
299
|
+
* read, so there is no such service.
|
|
300
|
+
*/
|
|
301
|
+
private readonly changeGate: IChangeReadGate,
|
|
244
302
|
/**
|
|
245
303
|
* Event bus that fans state changes out to connected SSE sessions.
|
|
246
304
|
* Optional so test harnesses / minimal boots can omit it — emits
|
|
@@ -265,7 +323,13 @@ export class WorkflowService implements IWorkflowService {
|
|
|
265
323
|
* exercise hooks) unchanged.
|
|
266
324
|
*/
|
|
267
325
|
public readonly hooks: WorkflowHooks = new WorkflowHooks(),
|
|
268
|
-
) {
|
|
326
|
+
) {
|
|
327
|
+
// The type already requires it; this holds for a caller the compiler did
|
|
328
|
+
// not see (plain JavaScript, a positional slip in a harness).
|
|
329
|
+
if (!changeGate || typeof changeGate.assertMayChange !== 'function') {
|
|
330
|
+
throw new Error('WorkflowService requires a read-before-write gate (IChangeReadGate)');
|
|
331
|
+
}
|
|
332
|
+
}
|
|
269
333
|
|
|
270
334
|
// ── Branches ──────────────────────────────────────────────────────────────
|
|
271
335
|
|
|
@@ -290,11 +354,11 @@ export class WorkflowService implements IWorkflowService {
|
|
|
290
354
|
.sweepOrphanedWorkspaces(branches.map((b) => b.name))
|
|
291
355
|
.then(({ removed }) => {
|
|
292
356
|
if (removed.length > 0) {
|
|
293
|
-
|
|
357
|
+
log.info(`swept ${removed.length} orphaned workspace(s):`, { removed });
|
|
294
358
|
}
|
|
295
359
|
})
|
|
296
360
|
.catch((err) => {
|
|
297
|
-
|
|
361
|
+
log.warn('orphan workspace sweep failed:', { err });
|
|
298
362
|
});
|
|
299
363
|
}
|
|
300
364
|
return branches;
|
|
@@ -453,10 +517,7 @@ export class WorkflowService implements IWorkflowService {
|
|
|
453
517
|
await this.workspaceService.deleteWorkspace(branchWorkspaceId);
|
|
454
518
|
}
|
|
455
519
|
} catch (err) {
|
|
456
|
-
|
|
457
|
-
`[workflow] could not retire workspace clone of deleted branch "${name}":`,
|
|
458
|
-
err,
|
|
459
|
-
);
|
|
520
|
+
log.warn(`could not retire workspace clone of deleted branch "${name}":`, { err });
|
|
460
521
|
}
|
|
461
522
|
}
|
|
462
523
|
|
|
@@ -510,11 +571,13 @@ export class WorkflowService implements IWorkflowService {
|
|
|
510
571
|
* and nothing else. The pull itself reports the change, since it is the
|
|
511
572
|
* only code that can observe it under the workspace mutex.
|
|
512
573
|
*/
|
|
513
|
-
private async pullWorkspace(workspaceId: string): Promise<
|
|
574
|
+
private async pullWorkspace(workspaceId: string): Promise<boolean> {
|
|
514
575
|
const { treeChanged } = await this.git.pull(workspaceId);
|
|
515
576
|
if (treeChanged && branchForWorkspaceId(workspaceId) === DEFAULT_BRANCH) {
|
|
516
577
|
this.events?.emit({ kind: 'fs-tree-changed', workspaceId, branch: DEFAULT_BRANCH });
|
|
578
|
+
return true;
|
|
517
579
|
}
|
|
580
|
+
return false;
|
|
518
581
|
}
|
|
519
582
|
|
|
520
583
|
async updateFromRemote(workspaceId: string, user?: AuthUser): Promise<void> {
|
|
@@ -569,6 +632,15 @@ export class WorkflowService implements IWorkflowService {
|
|
|
569
632
|
*/
|
|
570
633
|
private readonly owedAnnouncements = new Map<string, { after: string; changedPaths: string[] }>();
|
|
571
634
|
|
|
635
|
+
/**
|
|
636
|
+
* The latest RUNNING apply attempt per change-request number (see
|
|
637
|
+
* `beginApplyAttempt`). Entries leave when their attempt ends, so the map
|
|
638
|
+
* holds only attempts in flight, never every number anyone ever posted.
|
|
639
|
+
*/
|
|
640
|
+
private readonly applyAttempts = new Map<number, number>();
|
|
641
|
+
/** Process-wide attempt counter: a token is never reissued, even after its entry leaves. */
|
|
642
|
+
private applyAttemptSeq = 0;
|
|
643
|
+
|
|
572
644
|
async syncWorkspaceFromRemote(workspaceId: string): Promise<BranchSyncOutcome> {
|
|
573
645
|
const branch = branchForWorkspaceId(workspaceId);
|
|
574
646
|
const id = workspaceIdForBranch(branch);
|
|
@@ -594,7 +666,7 @@ export class WorkflowService implements IWorkflowService {
|
|
|
594
666
|
// is a different tree, and replaying the old paths against it would
|
|
595
667
|
// announce changes that never happened there.
|
|
596
668
|
this.owedAnnouncements.delete(id);
|
|
597
|
-
|
|
669
|
+
syncLog.info(`branch "${branch}" no longer exists on the host`);
|
|
598
670
|
return { branch, outcome: 'remote-gone' };
|
|
599
671
|
}
|
|
600
672
|
if (err instanceof PullRebaseConflictError) {
|
|
@@ -607,12 +679,12 @@ export class WorkflowService implements IWorkflowService {
|
|
|
607
679
|
// next clean pull or push.
|
|
608
680
|
await this.queuePullConflictRecovery(gitId, err);
|
|
609
681
|
const message = syncConflictMessage(branch, err.conflictedPaths);
|
|
610
|
-
|
|
682
|
+
syncLog.warn(message);
|
|
611
683
|
this.noteGitSyncFailed(gitId, branch, err, { paths: err.conflictedPaths, message });
|
|
612
684
|
return { branch, outcome: 'conflict', conflictedPaths: err.conflictedPaths, error: message };
|
|
613
685
|
}
|
|
614
686
|
const message = sanitizeError(err);
|
|
615
|
-
|
|
687
|
+
syncLog.warn(`pull failed for branch "${branch}": ${message}`);
|
|
616
688
|
this.noteGitSyncFailed(gitId, branch, err);
|
|
617
689
|
return { branch, outcome: 'error', error: message };
|
|
618
690
|
}
|
|
@@ -672,7 +744,7 @@ export class WorkflowService implements IWorkflowService {
|
|
|
672
744
|
// move HEAD again, so it must find the debt here rather than in the shas.
|
|
673
745
|
this.owedAnnouncements.set(id, announce);
|
|
674
746
|
const message = sanitizeError(err);
|
|
675
|
-
|
|
747
|
+
syncLog.warn(`pulled "${branch}" but could not announce it: ${message}`);
|
|
676
748
|
return { branch, outcome: 'error', error: message };
|
|
677
749
|
}
|
|
678
750
|
this.owedAnnouncements.delete(id);
|
|
@@ -719,15 +791,12 @@ export class WorkflowService implements IWorkflowService {
|
|
|
719
791
|
authorName: user?.name ?? RECOVERY_BOT_NAME,
|
|
720
792
|
});
|
|
721
793
|
if (queued) {
|
|
722
|
-
|
|
723
|
-
`
|
|
794
|
+
log.warn(
|
|
795
|
+
`pull conflict on ws=${workspaceId} branch=${err.branch} (${err.conflictedPaths.join(', ')}) — queued background recovery`,
|
|
724
796
|
);
|
|
725
797
|
}
|
|
726
798
|
} catch (queueErr) {
|
|
727
|
-
|
|
728
|
-
`[workflow] failed to queue pull-conflict recovery for ws=${workspaceId}:`,
|
|
729
|
-
queueErr instanceof Error ? queueErr.message : queueErr,
|
|
730
|
-
);
|
|
799
|
+
log.error(`failed to queue pull-conflict recovery for ws=${workspaceId}:`, { err: queueErr });
|
|
731
800
|
}
|
|
732
801
|
}
|
|
733
802
|
|
|
@@ -797,6 +866,23 @@ export class WorkflowService implements IWorkflowService {
|
|
|
797
866
|
return this.git.diffFileBetweenBranches(workspaceId, path, fromBranch, toBranch);
|
|
798
867
|
}
|
|
799
868
|
|
|
869
|
+
fileAtForkPoint(
|
|
870
|
+
workspaceId: string,
|
|
871
|
+
baseBranch: string,
|
|
872
|
+
sha: string,
|
|
873
|
+
path: string,
|
|
874
|
+
): Promise<string | null> {
|
|
875
|
+
return this.git.readFileAtForkPoint(workspaceId, baseBranch, sha, path);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
changeRequestForkPoint(
|
|
879
|
+
workspaceId: string,
|
|
880
|
+
baseBranch: string,
|
|
881
|
+
headBranch: string,
|
|
882
|
+
): Promise<string | null> {
|
|
883
|
+
return this.git.mergeBaseForPr(workspaceId, baseBranch, headBranch);
|
|
884
|
+
}
|
|
885
|
+
|
|
800
886
|
showFileAtChange(workspaceId: string, path: string, sha: string): Promise<string> {
|
|
801
887
|
return this.git.diffFileAtCommit(workspaceId, path, sha);
|
|
802
888
|
}
|
|
@@ -824,13 +910,23 @@ export class WorkflowService implements IWorkflowService {
|
|
|
824
910
|
* Always reads at-ref, never at the working tree, so a user can't broaden
|
|
825
911
|
* their own access by editing `roles.yaml` / `access.md` in the same
|
|
826
912
|
* session.
|
|
913
|
+
*
|
|
914
|
+
* Resolves to `'restore'` when the write passed ONLY as a platform-file
|
|
915
|
+
* restore — the caller then knows the destination denies this admin, and
|
|
916
|
+
* the read gate that follows stands aside for the same rescue.
|
|
827
917
|
*/
|
|
828
918
|
private async assertCanWriteAtPath(
|
|
829
919
|
workspaceId: string,
|
|
830
920
|
branch: string,
|
|
831
921
|
userEmail: string,
|
|
832
922
|
targetPath: string,
|
|
833
|
-
|
|
923
|
+
/**
|
|
924
|
+
* The caller says this write is an admin putting a misplaced platform file
|
|
925
|
+
* back, and names where the file is coming from. Checked in both halves,
|
|
926
|
+
* never taken on trust — see `acquireLock`'s `opts`.
|
|
927
|
+
*/
|
|
928
|
+
platformRestore?: { source: string },
|
|
929
|
+
): Promise<'granted' | 'restore'> {
|
|
834
930
|
// The lock route passes workspace-relative paths
|
|
835
931
|
// (`knowledge-base/GTM/.../Foo.md`), but the access model is keyed by
|
|
836
932
|
// *repo-relative* paths — `git ls-tree` runs inside the inner repo dir,
|
|
@@ -840,17 +936,36 @@ export class WorkflowService implements IWorkflowService {
|
|
|
840
936
|
// typically only grants Admin → non-admins get 403'd on files their
|
|
841
937
|
// role's nested access.md actually permits. Mirrors the frontend's
|
|
842
938
|
// `useFileAccess` prefix-strip on the way INTO the API.
|
|
843
|
-
const
|
|
844
|
-
?
|
|
845
|
-
|
|
939
|
+
const toRepoRelative = (p: string): string =>
|
|
940
|
+
p.startsWith(`${this.kbDirName}/`) ? p.slice(this.kbDirName.length + 1) : p;
|
|
941
|
+
const repoRelative = toRepoRelative(targetPath);
|
|
846
942
|
const result = await this.accessControl.canWriteBatchAtRef(
|
|
847
943
|
workspaceId,
|
|
848
944
|
`HEAD`,
|
|
849
945
|
userEmail,
|
|
850
946
|
[repoRelative],
|
|
851
947
|
);
|
|
852
|
-
if (!result) return; // no config at ref → default-allow (bootstrap)
|
|
853
|
-
if (result.get(repoRelative)) return;
|
|
948
|
+
if (!result) return 'granted'; // no config at ref → default-allow (bootstrap)
|
|
949
|
+
if (result.get(repoRelative)) return 'granted';
|
|
950
|
+
// The one write allowed past a destination that denies it: an admin
|
|
951
|
+
// putting a platform file back where the platform reads it. The access
|
|
952
|
+
// module decides who — a repository whose root `access.md` is the file
|
|
953
|
+
// that went missing denies everyone, including the admin who would
|
|
954
|
+
// restore it — and the shape check decides WHICH MOVE may even ask.
|
|
955
|
+
//
|
|
956
|
+
// Both are asked here rather than trusted from the route, because
|
|
957
|
+
// `canRestorePlatformFile` answers only on where the write LANDS: on its
|
|
958
|
+
// own it would approve carrying the root's own `access.md` into a folder
|
|
959
|
+
// that has none, which is the loss this feature exists to prevent, spelled
|
|
960
|
+
// as its own rescue. The source is part of the claim precisely so no
|
|
961
|
+
// future caller can make that claim by passing a flag.
|
|
962
|
+
if (
|
|
963
|
+
typeof platformRestore?.source === 'string' &&
|
|
964
|
+
isPlatformRestoreShape(toRepoRelative(platformRestore.source), repoRelative) &&
|
|
965
|
+
(await this.accessControl.canRestorePlatformFile(workspaceId, userEmail, repoRelative))
|
|
966
|
+
) {
|
|
967
|
+
return 'restore';
|
|
968
|
+
}
|
|
854
969
|
const eligible = await this.accessControl.eligibleWritersAtRef(
|
|
855
970
|
workspaceId,
|
|
856
971
|
`HEAD`,
|
|
@@ -865,13 +980,26 @@ export class WorkflowService implements IWorkflowService {
|
|
|
865
980
|
|
|
866
981
|
// ── File locks ────────────────────────────────────────────────────────────
|
|
867
982
|
|
|
983
|
+
// Every method below canonicalises the caller's spelling into ONE file
|
|
984
|
+
// identity before it does anything with it. `FileLockService` canonicalises
|
|
985
|
+
// too, and has to: it is the coordination point and a caller can reach it
|
|
986
|
+
// without coming through here. The reason to do it again at this layer is
|
|
987
|
+
// that the path does not only key a lock row here. It is also what the
|
|
988
|
+
// permission gate is evaluated against, what `commitFile` stages, what the
|
|
989
|
+
// commit queue enqueues, and what rides out on every `lock-*` event. Left
|
|
990
|
+
// raw, a checkpoint on `./x//a.md` would find its lock and then fail at the
|
|
991
|
+
// commit, and a release would enqueue a row the worker keys differently from
|
|
992
|
+
// the lock it just dropped. Canonicalising is idempotent, so the second pass
|
|
993
|
+
// inside the lock service is free.
|
|
994
|
+
|
|
868
995
|
async acquireLock(
|
|
869
996
|
workspaceId: string,
|
|
870
997
|
branch: string,
|
|
871
|
-
|
|
998
|
+
rawPath: string,
|
|
872
999
|
user: AuthUser,
|
|
873
|
-
opts?: { coordination?: boolean },
|
|
1000
|
+
opts?: { coordination?: boolean; platformRestore?: { source: string } },
|
|
874
1001
|
): Promise<AcquireLockResult> {
|
|
1002
|
+
const targetPath = canonicalFileIdentity(rawPath);
|
|
875
1003
|
// **Permission check at lock acquisition, not at commit time.** Under the
|
|
876
1004
|
// "disk is the source of truth" rule, once a write has landed on disk we
|
|
877
1005
|
// must never reject the commit that publishes it — otherwise we'd be
|
|
@@ -881,14 +1009,24 @@ export class WorkflowService implements IWorkflowService {
|
|
|
881
1009
|
// the lock and the editor never opens. Once the lock is in hand, the
|
|
882
1010
|
// commit + push pipeline does not re-check (see `commitFile`).
|
|
883
1011
|
//
|
|
884
|
-
// **
|
|
885
|
-
// feature/draft branches anyone can write; canonical state
|
|
886
|
-
// through change-request approval, which is where the real
|
|
887
|
-
// boundary lives. Checking at HEAD (not at the working tree) so a
|
|
888
|
-
// can't grant themselves access by editing `roles.yaml` in the same
|
|
1012
|
+
// **Write gate — protected branches only** (mirrors the legacy commit-time
|
|
1013
|
+
// gate). On feature/draft branches anyone can write; canonical state
|
|
1014
|
+
// changes go through change-request approval, which is where the real
|
|
1015
|
+
// security boundary lives. Checking at HEAD (not at the working tree) so a
|
|
1016
|
+
// user can't grant themselves access by editing `roles.yaml` in the same
|
|
889
1017
|
// session.
|
|
890
1018
|
//
|
|
891
|
-
// **
|
|
1019
|
+
// **Read gate — every branch.** Nothing is created, changed or removed
|
|
1020
|
+
// where its author cannot read, drafts included: a proposal that lands in
|
|
1021
|
+
// a folder its author cannot see would vanish from them the moment it was
|
|
1022
|
+
// added, and a write grant only implies read — a nearer `deny read` can
|
|
1023
|
+
// take the read away while the write stands. The one exception, a new
|
|
1024
|
+
// folder directly under one of the three roots, and the reasons are in
|
|
1025
|
+
// `access-model/change-gate.ts`. A write that passed only as a
|
|
1026
|
+
// platform-file restore is not asked: that rescue exists exactly for a
|
|
1027
|
+
// destination whose rules deny the admin making it.
|
|
1028
|
+
//
|
|
1029
|
+
// **Coordination acquires skip both gates** (see the interface doc): the
|
|
892
1030
|
// caller wants only mutual exclusion with the path's writer — e.g. the
|
|
893
1031
|
// roles admin holding machine-owned `synced-groups.yaml` steady across
|
|
894
1032
|
// its IdP-mode recheck — and will never write the path. Gating those on
|
|
@@ -897,15 +1035,27 @@ export class WorkflowService implements IWorkflowService {
|
|
|
897
1035
|
// authority flows from the hold: the mode is persisted on the lock row
|
|
898
1036
|
// and `commitFileWhileLocked` / `releaseLock` refuse to treat a
|
|
899
1037
|
// coordination hold as write possession (see those methods).
|
|
900
|
-
if (
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
1038
|
+
if (!opts?.coordination) {
|
|
1039
|
+
let via: 'granted' | 'restore' = 'granted';
|
|
1040
|
+
if (isProtectedBranch(branch)) {
|
|
1041
|
+
// `assertCanWriteAtPath` throws AccessDeniedError on denial, with the
|
|
1042
|
+
// eligible-writers payload so the frontend can render a useful refusal.
|
|
1043
|
+
via = await this.assertCanWriteAtPath(
|
|
1044
|
+
workspaceId,
|
|
1045
|
+
branch,
|
|
1046
|
+
user.email,
|
|
1047
|
+
targetPath,
|
|
1048
|
+
opts?.platformRestore,
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
if (via !== 'restore') {
|
|
1052
|
+
await this.changeGate.assertMayChange(workspaceId, user.email, targetPath, 'file');
|
|
1053
|
+
}
|
|
904
1054
|
}
|
|
905
1055
|
const result = await this.fileLocks.acquire(workspaceId, branch, targetPath, user, opts);
|
|
906
1056
|
if (result.acquired) {
|
|
907
|
-
|
|
908
|
-
`
|
|
1057
|
+
lockLog.info(
|
|
1058
|
+
`ACQUIRE ws=${workspaceId} branch=${branch} path=${targetPath} user=${user.id} → acquired`,
|
|
909
1059
|
);
|
|
910
1060
|
// Only fire on successful acquisition — a contention "false" return
|
|
911
1061
|
// means nothing observable changed for other users (someone else
|
|
@@ -920,8 +1070,8 @@ export class WorkflowService implements IWorkflowService {
|
|
|
920
1070
|
holderName: user.name,
|
|
921
1071
|
});
|
|
922
1072
|
} else {
|
|
923
|
-
|
|
924
|
-
`
|
|
1073
|
+
lockLog.info(
|
|
1074
|
+
`ACQUIRE ws=${workspaceId} branch=${branch} path=${targetPath} user=${user.id} → contended, held by ${result.lock.holderName} (${result.lock.holderUserId})`,
|
|
925
1075
|
);
|
|
926
1076
|
}
|
|
927
1077
|
return result;
|
|
@@ -930,18 +1080,19 @@ export class WorkflowService implements IWorkflowService {
|
|
|
930
1080
|
async heartbeatLock(
|
|
931
1081
|
workspaceId: string,
|
|
932
1082
|
branch: string,
|
|
933
|
-
|
|
1083
|
+
rawPath: string,
|
|
934
1084
|
user: AuthUser,
|
|
935
1085
|
): Promise<FileLock> {
|
|
1086
|
+
const targetPath = canonicalFileIdentity(rawPath);
|
|
936
1087
|
try {
|
|
937
1088
|
const lock = await this.fileLocks.heartbeat(workspaceId, branch, targetPath, user);
|
|
938
|
-
|
|
939
|
-
`
|
|
1089
|
+
lockLog.info(
|
|
1090
|
+
`HEARTBEAT ws=${workspaceId} branch=${branch} path=${targetPath} user=${user.id} → ok (expires ${lock.expiresAt})`,
|
|
940
1091
|
);
|
|
941
1092
|
return lock;
|
|
942
1093
|
} catch (err) {
|
|
943
|
-
|
|
944
|
-
`
|
|
1094
|
+
lockLog.warn(
|
|
1095
|
+
`HEARTBEAT ws=${workspaceId} branch=${branch} path=${targetPath} user=${user.id} → FAIL ${err instanceof Error ? err.message : err}`,
|
|
945
1096
|
);
|
|
946
1097
|
throw err;
|
|
947
1098
|
}
|
|
@@ -957,10 +1108,11 @@ export class WorkflowService implements IWorkflowService {
|
|
|
957
1108
|
async commitFileWhileLocked(
|
|
958
1109
|
workspaceId: string,
|
|
959
1110
|
branch: string,
|
|
960
|
-
|
|
1111
|
+
rawPath: string,
|
|
961
1112
|
user: AuthUser,
|
|
962
1113
|
summary?: string,
|
|
963
1114
|
): Promise<Change | null> {
|
|
1115
|
+
const targetPath = canonicalFileIdentity(rawPath);
|
|
964
1116
|
const lock = await this.fileLocks.get(workspaceId, branch, targetPath);
|
|
965
1117
|
if (!lock || lock.holderUserId !== user.id) {
|
|
966
1118
|
throw new WorkflowValidationError(
|
|
@@ -1011,9 +1163,9 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1011
1163
|
this.noteGitSyncOk(workspaceId, branch);
|
|
1012
1164
|
} catch (recoveryErr) {
|
|
1013
1165
|
recoveryError = recoveryErr;
|
|
1014
|
-
|
|
1015
|
-
'
|
|
1016
|
-
|
|
1166
|
+
log.warn(
|
|
1167
|
+
'autosave cooperative recovery (pull-rebase) failed; leaving the unpushed commit for the next save / releaseLock to surface:',
|
|
1168
|
+
{ err: recoveryErr },
|
|
1017
1169
|
);
|
|
1018
1170
|
}
|
|
1019
1171
|
}
|
|
@@ -1032,10 +1184,7 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1032
1184
|
if (!looksLikeNonFastForward) {
|
|
1033
1185
|
this.noteGitSyncFailed(workspaceId, branch, recoveryError ?? err);
|
|
1034
1186
|
}
|
|
1035
|
-
|
|
1036
|
-
'[workflow] push after autosave commit failed (commit landed locally):',
|
|
1037
|
-
detail,
|
|
1038
|
-
);
|
|
1187
|
+
log.warn('push after autosave commit failed (commit landed locally):', { detail });
|
|
1039
1188
|
}
|
|
1040
1189
|
}
|
|
1041
1190
|
this.events?.emit({
|
|
@@ -1084,21 +1233,20 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1084
1233
|
async releaseLock(
|
|
1085
1234
|
workspaceId: string,
|
|
1086
1235
|
branch: string,
|
|
1087
|
-
|
|
1236
|
+
rawPath: string,
|
|
1088
1237
|
user: AuthUser,
|
|
1089
1238
|
): Promise<void> {
|
|
1239
|
+
const targetPath = canonicalFileIdentity(rawPath);
|
|
1090
1240
|
// Ownership check — the lock service's `release` is idempotent and
|
|
1091
1241
|
// would silently no-op for a non-holder, but we'd still enqueue a
|
|
1092
1242
|
// commit attributed to whoever called us. The guard rejects callers
|
|
1093
1243
|
// who don't actually hold the row so impersonation can't enqueue
|
|
1094
1244
|
// commits as a third party.
|
|
1095
|
-
|
|
1096
|
-
`[lock] RELEASE start ws=${workspaceId} branch=${branch} path=${targetPath} user=${user.id}`,
|
|
1097
|
-
);
|
|
1245
|
+
lockLog.info(`RELEASE start ws=${workspaceId} branch=${branch} path=${targetPath} user=${user.id}`);
|
|
1098
1246
|
const lock = await this.fileLocks.get(workspaceId, branch, targetPath);
|
|
1099
1247
|
if (!lock || lock.holderUserId !== user.id) {
|
|
1100
|
-
|
|
1101
|
-
`
|
|
1248
|
+
lockLog.warn(
|
|
1249
|
+
`RELEASE refused ws=${workspaceId} branch=${branch} path=${targetPath} user=${user.id} → ${lock ? `held by ${lock.holderName} (${lock.holderUserId})` : 'no lock row'}`,
|
|
1102
1250
|
);
|
|
1103
1251
|
throw new WorkflowValidationError(
|
|
1104
1252
|
`Cannot release lock on "${targetPath}": not held by you (or no longer exists).`,
|
|
@@ -1115,8 +1263,8 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1115
1263
|
// and `releaseLockUntouched`. Worst case a refused caller strands the
|
|
1116
1264
|
// row until its TTL.
|
|
1117
1265
|
if (lock.mode === 'coordination') {
|
|
1118
|
-
|
|
1119
|
-
`
|
|
1266
|
+
lockLog.warn(
|
|
1267
|
+
`RELEASE refused ws=${workspaceId} branch=${branch} path=${targetPath} user=${user.id} → coordination hold (no commit may be enqueued)`,
|
|
1120
1268
|
);
|
|
1121
1269
|
throw new WorkflowValidationError(
|
|
1122
1270
|
`Cannot release lock on "${targetPath}" with a commit: it is a coordination hold. Use releaseLockNoCommit.`,
|
|
@@ -1141,8 +1289,8 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1141
1289
|
authorName: user.name,
|
|
1142
1290
|
});
|
|
1143
1291
|
await this.fileLocks.release(workspaceId, branch, targetPath, user);
|
|
1144
|
-
|
|
1145
|
-
`
|
|
1292
|
+
lockLog.info(
|
|
1293
|
+
`RELEASE done ws=${workspaceId} branch=${branch} path=${targetPath} user=${user.id} (commit queued for background worker)`,
|
|
1146
1294
|
);
|
|
1147
1295
|
// The `file-changed` SSE event used to fire here after the synchronous
|
|
1148
1296
|
// commit landed, carrying the new sha. Under the queue model the
|
|
@@ -1244,7 +1392,7 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1244
1392
|
// emitted id so every consumer sees one spelling.
|
|
1245
1393
|
const id = branchForWorkspaceId(workspaceId);
|
|
1246
1394
|
if (!this.gitSyncFailing.delete(id)) return;
|
|
1247
|
-
|
|
1395
|
+
log.info(`git sync recovered for workspace=${id} branch=${branch}`);
|
|
1248
1396
|
this.events?.emit({ kind: 'git-sync-recovered', workspaceId: id, branch });
|
|
1249
1397
|
}
|
|
1250
1398
|
|
|
@@ -1357,15 +1505,15 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1357
1505
|
await this.git.push(workspaceId, user, opts);
|
|
1358
1506
|
recovered = true;
|
|
1359
1507
|
this.noteGitSyncOk(workspaceId, branch);
|
|
1360
|
-
|
|
1361
|
-
`
|
|
1508
|
+
log.info(
|
|
1509
|
+
`non-fast-forward push recovered via pull --rebase for workspace=${workspaceId} branch=${branch} path=${targetPath}`,
|
|
1362
1510
|
);
|
|
1363
1511
|
} catch (recoveryErr) {
|
|
1364
1512
|
recoveryError = recoveryErr;
|
|
1365
1513
|
recoveryDetail = recoveryErr instanceof Error ? recoveryErr.message : String(recoveryErr);
|
|
1366
|
-
|
|
1367
|
-
`
|
|
1368
|
-
recoveryDetail,
|
|
1514
|
+
log.warn(
|
|
1515
|
+
`cooperative recovery (pull-rebase) failed for workspace=${workspaceId} user=${user.id}; handing off to agent:`,
|
|
1516
|
+
{ detail: recoveryDetail },
|
|
1369
1517
|
);
|
|
1370
1518
|
}
|
|
1371
1519
|
}
|
|
@@ -1378,9 +1526,9 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1378
1526
|
// first rejection may be a stale non-fast-forward the pull already
|
|
1379
1527
|
// cured); when it was skipped, the first error is all there is.
|
|
1380
1528
|
this.noteGitSyncFailed(workspaceId, branch, recoveryError ?? firstPushErr);
|
|
1381
|
-
|
|
1382
|
-
`
|
|
1383
|
-
firstDetail,
|
|
1529
|
+
log.warn(
|
|
1530
|
+
`push failed for workspace=${workspaceId} user=${user.id}; throwing PushNeedsAgentResolutionError so the frontend can hand off to the agent:`,
|
|
1531
|
+
{ detail: firstDetail },
|
|
1384
1532
|
);
|
|
1385
1533
|
throw new PushNeedsAgentResolutionError(branch, targetPath, firstDetail, recoveryDetail);
|
|
1386
1534
|
}
|
|
@@ -1399,22 +1547,23 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1399
1547
|
async releaseLockNoCommit(
|
|
1400
1548
|
workspaceId: string,
|
|
1401
1549
|
branch: string,
|
|
1402
|
-
|
|
1550
|
+
rawPath: string,
|
|
1403
1551
|
user: AuthUser,
|
|
1404
1552
|
): Promise<void> {
|
|
1553
|
+
const targetPath = canonicalFileIdentity(rawPath);
|
|
1405
1554
|
// Verify ownership first so the emit only fires when something
|
|
1406
1555
|
// observable actually changed. `fileLocks.release` silently no-ops
|
|
1407
1556
|
// when the caller doesn't hold the row (idempotent-by-design), so
|
|
1408
1557
|
// without this guard a non-holder calling this method would emit
|
|
1409
1558
|
// `lock-released` and trick every other client into clearing their
|
|
1410
1559
|
// "Locked by X" banner even though the lock is still held.
|
|
1411
|
-
|
|
1412
|
-
`
|
|
1560
|
+
lockLog.info(
|
|
1561
|
+
`RELEASE-NO-COMMIT start ws=${workspaceId} branch=${branch} path=${targetPath} user=${user.id}`,
|
|
1413
1562
|
);
|
|
1414
1563
|
const lock = await this.fileLocks.get(workspaceId, branch, targetPath);
|
|
1415
1564
|
if (!lock || lock.holderUserId !== user.id) {
|
|
1416
|
-
|
|
1417
|
-
`
|
|
1565
|
+
lockLog.info(
|
|
1566
|
+
`RELEASE-NO-COMMIT no-op ws=${workspaceId} path=${targetPath} → ${lock ? `held by ${lock.holderName}` : 'no lock row'}`,
|
|
1418
1567
|
);
|
|
1419
1568
|
return;
|
|
1420
1569
|
}
|
|
@@ -1442,10 +1591,9 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1442
1591
|
} catch (err) {
|
|
1443
1592
|
// Queue unreadable — fall back to the discard (the strict default:
|
|
1444
1593
|
// never let a coordination hold end with publishable stray bytes).
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
);
|
|
1594
|
+
log.warn(`pending-commit lookup failed for workspace=${workspaceId} path=${targetPath}; discarding:`, {
|
|
1595
|
+
err,
|
|
1596
|
+
});
|
|
1449
1597
|
}
|
|
1450
1598
|
}
|
|
1451
1599
|
let discarded = false;
|
|
@@ -1457,15 +1605,14 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1457
1605
|
// Best-effort: a discard failure is logged but doesn't block the
|
|
1458
1606
|
// lock release. Worst case the working tree stays dirty for one
|
|
1459
1607
|
// path until the next save on it cleans up.
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
);
|
|
1608
|
+
log.warn(`discardPath failed for workspace=${workspaceId} branch=${branch} path=${targetPath}:`, {
|
|
1609
|
+
err,
|
|
1610
|
+
});
|
|
1464
1611
|
}
|
|
1465
1612
|
}
|
|
1466
1613
|
await this.fileLocks.release(workspaceId, branch, targetPath, user);
|
|
1467
|
-
|
|
1468
|
-
`
|
|
1614
|
+
lockLog.info(
|
|
1615
|
+
`RELEASE-NO-COMMIT done ws=${workspaceId} branch=${branch} path=${targetPath} user=${user.id} → ${discarded ? 'discarded + released' : 'released (working tree untouched)'}`,
|
|
1469
1616
|
);
|
|
1470
1617
|
this.events?.emit({
|
|
1471
1618
|
kind: 'lock-released',
|
|
@@ -1504,22 +1651,23 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1504
1651
|
async releaseLockUntouched(
|
|
1505
1652
|
workspaceId: string,
|
|
1506
1653
|
branch: string,
|
|
1507
|
-
|
|
1654
|
+
rawPath: string,
|
|
1508
1655
|
user: AuthUser,
|
|
1509
1656
|
): Promise<void> {
|
|
1510
|
-
|
|
1511
|
-
|
|
1657
|
+
const targetPath = canonicalFileIdentity(rawPath);
|
|
1658
|
+
lockLog.info(
|
|
1659
|
+
`RELEASE-UNTOUCHED start ws=${workspaceId} branch=${branch} path=${targetPath} user=${user.id}`,
|
|
1512
1660
|
);
|
|
1513
1661
|
const lock = await this.fileLocks.get(workspaceId, branch, targetPath);
|
|
1514
1662
|
if (!lock || lock.holderUserId !== user.id) {
|
|
1515
|
-
|
|
1516
|
-
`
|
|
1663
|
+
lockLog.info(
|
|
1664
|
+
`RELEASE-UNTOUCHED no-op ws=${workspaceId} path=${targetPath} → ${lock ? `held by ${lock.holderName}` : 'no lock row'}`,
|
|
1517
1665
|
);
|
|
1518
1666
|
return;
|
|
1519
1667
|
}
|
|
1520
1668
|
await this.fileLocks.release(workspaceId, branch, targetPath, user);
|
|
1521
|
-
|
|
1522
|
-
`
|
|
1669
|
+
lockLog.info(
|
|
1670
|
+
`RELEASE-UNTOUCHED done ws=${workspaceId} branch=${branch} path=${targetPath} user=${user.id} → released (disk + queue untouched)`,
|
|
1523
1671
|
);
|
|
1524
1672
|
this.events?.emit({
|
|
1525
1673
|
kind: 'lock-released',
|
|
@@ -1531,8 +1679,10 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1531
1679
|
// nothing on disk moved.
|
|
1532
1680
|
}
|
|
1533
1681
|
|
|
1534
|
-
hasQueuedCommit(workspaceId: string, branch: string,
|
|
1535
|
-
|
|
1682
|
+
hasQueuedCommit(workspaceId: string, branch: string, rawPath: string): Promise<boolean> {
|
|
1683
|
+
// The row was stored under the canonical identity (`releaseLock`), so the
|
|
1684
|
+
// question is asked under it too — whatever spelling the caller has.
|
|
1685
|
+
return this.pendingCommits.hasLiveRowFor(workspaceId, branch, canonicalFileIdentity(rawPath));
|
|
1536
1686
|
}
|
|
1537
1687
|
|
|
1538
1688
|
hasUnpushedCommits(workspaceId: string): Promise<boolean> {
|
|
@@ -1542,9 +1692,9 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1542
1692
|
getLock(
|
|
1543
1693
|
workspaceId: string,
|
|
1544
1694
|
branch: string,
|
|
1545
|
-
|
|
1695
|
+
rawPath: string,
|
|
1546
1696
|
): Promise<FileLock | null> {
|
|
1547
|
-
return this.fileLocks.get(workspaceId, branch,
|
|
1697
|
+
return this.fileLocks.get(workspaceId, branch, canonicalFileIdentity(rawPath));
|
|
1548
1698
|
}
|
|
1549
1699
|
|
|
1550
1700
|
// ── Change Requests ───────────────────────────────────────────────────────
|
|
@@ -1700,7 +1850,7 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1700
1850
|
targetBranch: input.targetBranch,
|
|
1701
1851
|
title: input.title.trim(),
|
|
1702
1852
|
body,
|
|
1703
|
-
authorEmail: user.email
|
|
1853
|
+
authorEmail: canonicalEmail(user.email),
|
|
1704
1854
|
authorName: user.name,
|
|
1705
1855
|
})
|
|
1706
1856
|
.returning({ number: changeRequests.number });
|
|
@@ -1782,10 +1932,20 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1782
1932
|
* as `ChangeRequestConflictsError`; the caller (UI or agent) is expected
|
|
1783
1933
|
* to route into the resolution flow exactly as on open.
|
|
1784
1934
|
*
|
|
1785
|
-
* Approvals
|
|
1786
|
-
*
|
|
1787
|
-
*
|
|
1788
|
-
*
|
|
1935
|
+
* Approvals are carried across the merge BY CONTENT: every approval on a
|
|
1936
|
+
* file whose bytes the merge left alone is re-pinned to the new head, and
|
|
1937
|
+
* only a file the merge actually changed loses its approval (staleness is
|
|
1938
|
+
* still `row.headSha !== headSha`, unchanged). Without this, a merge
|
|
1939
|
+
* nobody asked for would void every approval on the request — including
|
|
1940
|
+
* the files it never touched — and since the dialog now runs this update
|
|
1941
|
+
* by itself, every stale request would cost a second round of review.
|
|
1942
|
+
* This holds for EVERY caller of the route, agent or UI.
|
|
1943
|
+
*
|
|
1944
|
+
* Authority: the request's author, or anyone who may apply it — the
|
|
1945
|
+
* detail's `viewerCanUpdate`, computed for THIS caller, so the button and
|
|
1946
|
+
* the enforcement read one predicate. A conflicting merge is aborted
|
|
1947
|
+
* inside `mergeFromOrigin`: nothing is committed or pushed, and the branch
|
|
1948
|
+
* is exactly what it was.
|
|
1789
1949
|
*/
|
|
1790
1950
|
async updateFromTarget(
|
|
1791
1951
|
workspaceId: string,
|
|
@@ -1805,6 +1965,51 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1805
1965
|
{ kind: 'change-request-not-open', state: detail.state },
|
|
1806
1966
|
);
|
|
1807
1967
|
}
|
|
1968
|
+
if (!detail.viewerCanUpdate) {
|
|
1969
|
+
throw new WorkflowDomainError(
|
|
1970
|
+
'Only the author of this change request, or someone who may apply it, can update it.',
|
|
1971
|
+
403,
|
|
1972
|
+
);
|
|
1973
|
+
}
|
|
1974
|
+
// Taken BEFORE the head moves (the pull below can move it too): only a
|
|
1975
|
+
// refusal recorded earlier describes the revision this replaces (see
|
|
1976
|
+
// `clearApplyFailure`).
|
|
1977
|
+
const headMovedAfter = new Date();
|
|
1978
|
+
// Freshen the source checkout first, and let a failure stop the Update:
|
|
1979
|
+
// merging onto a head behind its own origin branch would leave a local
|
|
1980
|
+
// merge commit that then cannot push, stranding it in the workspace. A
|
|
1981
|
+
// rebase conflict hands the stranded saves to recovery, as every other
|
|
1982
|
+
// pull does, before the refusal reaches the caller.
|
|
1983
|
+
try {
|
|
1984
|
+
await this.pullWorkspace(workspaceId);
|
|
1985
|
+
} catch (err) {
|
|
1986
|
+
if (err instanceof PullRebaseConflictError) {
|
|
1987
|
+
await this.queuePullConflictRecovery(workspaceId, err, user);
|
|
1988
|
+
}
|
|
1989
|
+
throw err;
|
|
1990
|
+
}
|
|
1991
|
+
// The head an approval is pinned to is whatever is PUBLISHED on the
|
|
1992
|
+
// request's branch — and the pull above may just have moved it: an agent,
|
|
1993
|
+
// or the author from another client, can push while this dialog is still
|
|
1994
|
+
// reading the detail. Resolve it HERE rather than trusting the
|
|
1995
|
+
// `detail.headSha` read before the pull: an approval made against that
|
|
1996
|
+
// newer head carries a sha the pre-pull detail does not name, so carrying
|
|
1997
|
+
// forward from the stale one would find no rows and void every approval
|
|
1998
|
+
// the merge never touched.
|
|
1999
|
+
//
|
|
2000
|
+
// Best effort, like the carry-forward it feeds: the bookkeeping must never
|
|
2001
|
+
// be what stops a request from being brought up to date, so a failed
|
|
2002
|
+
// resolve falls back to the sha the detail reported.
|
|
2003
|
+
let headBeforeMerge = detail.headSha;
|
|
2004
|
+
try {
|
|
2005
|
+
const at = await this.git.resolvePrShas(workspaceId, detail.base, detail.branch);
|
|
2006
|
+
headBeforeMerge = at.headSha;
|
|
2007
|
+
} catch (err) {
|
|
2008
|
+
log.warn(
|
|
2009
|
+
`could not resolve the published head of change request #${number} before updating it from target; falling back to the head its detail reported`,
|
|
2010
|
+
{ err },
|
|
2011
|
+
);
|
|
2012
|
+
}
|
|
1808
2013
|
const outcome = await this.git.mergeFromOrigin(
|
|
1809
2014
|
workspaceId,
|
|
1810
2015
|
detail.branch,
|
|
@@ -1818,6 +2023,8 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1818
2023
|
// already up to date there's nothing to share — short-circuit the push.
|
|
1819
2024
|
if (!outcome.alreadyUpToDate) {
|
|
1820
2025
|
await this.trackedPush(workspaceId, user);
|
|
2026
|
+
// The source head moved: the refusal described a revision that is gone.
|
|
2027
|
+
await this.clearApplyFailure(number, { recordedBefore: headMovedAfter });
|
|
1821
2028
|
}
|
|
1822
2029
|
this.prs.invalidateDetailCache(number);
|
|
1823
2030
|
const refreshed = await this.prs.getPrDetail(number, {
|
|
@@ -1830,6 +2037,51 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1830
2037
|
`Refreshed change request #${number} but could not re-fetch its detail.`,
|
|
1831
2038
|
);
|
|
1832
2039
|
}
|
|
2040
|
+
// The merge is a commit the APPROVERS did not make. Re-pin their
|
|
2041
|
+
// approvals onto the new head for every file whose bytes it left alone —
|
|
2042
|
+
// git's own two-dot diff between the two heads is the verdict on which
|
|
2043
|
+
// those are. Best effort: a request that IS up to date is worth far more
|
|
2044
|
+
// than the bookkeeping, so a failure here is logged and the fresh detail
|
|
2045
|
+
// still goes back.
|
|
2046
|
+
//
|
|
2047
|
+
// `alreadyUpToDate` is the real gate: no merge commit, no head to compare,
|
|
2048
|
+
// nothing moved under anyone. The other two conditions are belt-and-braces
|
|
2049
|
+
// over a sha that is not a sha — a resolve that failed onto a detail with
|
|
2050
|
+
// no head, or two heads that somehow read equal — both of which
|
|
2051
|
+
// `pathsChangedBetween` and `carryApprovalsForward` would refuse anyway.
|
|
2052
|
+
// A merge that authored a commit cannot leave the head where it was.
|
|
2053
|
+
if (!outcome.alreadyUpToDate && headBeforeMerge && refreshed.headSha !== headBeforeMerge) {
|
|
2054
|
+
try {
|
|
2055
|
+
const changedPaths = await this.git.pathsChangedBetween(
|
|
2056
|
+
workspaceId,
|
|
2057
|
+
headBeforeMerge,
|
|
2058
|
+
refreshed.headSha,
|
|
2059
|
+
);
|
|
2060
|
+
const carried = await this.reviewWorkflow.carryApprovalsForward(
|
|
2061
|
+
number,
|
|
2062
|
+
headBeforeMerge,
|
|
2063
|
+
refreshed.headSha,
|
|
2064
|
+
changedPaths,
|
|
2065
|
+
);
|
|
2066
|
+
// The detail above was assembled before those rows existed, so its
|
|
2067
|
+
// `approvals` (and the merge gate derived from them) still describe
|
|
2068
|
+
// the pre-merge head. Only re-read when something actually carried.
|
|
2069
|
+
if (carried > 0) {
|
|
2070
|
+
this.prs.invalidateDetailCache(number);
|
|
2071
|
+
const withApprovals = await this.prs.getPrDetail(number, {
|
|
2072
|
+
fresh: true,
|
|
2073
|
+
workspaceId,
|
|
2074
|
+
viewerEmail: user.email,
|
|
2075
|
+
});
|
|
2076
|
+
if (withApprovals) return withApprovals;
|
|
2077
|
+
}
|
|
2078
|
+
} catch (err) {
|
|
2079
|
+
log.warn(
|
|
2080
|
+
`could not carry approvals forward on change request #${number} after updating from target`,
|
|
2081
|
+
{ err },
|
|
2082
|
+
);
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
1833
2085
|
return refreshed;
|
|
1834
2086
|
}
|
|
1835
2087
|
|
|
@@ -1874,6 +2126,8 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1874
2126
|
authorIdHash: string | null,
|
|
1875
2127
|
workspaceId: string,
|
|
1876
2128
|
): Promise<FileApproval[]> {
|
|
2129
|
+
// Taken BEFORE the approval lands (see `clearApplyFailure`).
|
|
2130
|
+
const approvedAfter = new Date();
|
|
1877
2131
|
const approvals = await this.reviewWorkflow.approveFile(
|
|
1878
2132
|
number,
|
|
1879
2133
|
path,
|
|
@@ -1884,6 +2138,14 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1884
2138
|
authorIdHash,
|
|
1885
2139
|
workspaceId,
|
|
1886
2140
|
);
|
|
2141
|
+
// A recorded approval can answer a GATE refusal only — a conflict or a git
|
|
2142
|
+
// error still describes the request — and only once the gate that refused
|
|
2143
|
+
// would now pass: approving one file of several leaves the rest waiting,
|
|
2144
|
+
// and the refusal naming them still stands. Warnings count too, since an
|
|
2145
|
+
// apply without bypass is refused on them.
|
|
2146
|
+
if (this.gateWouldPass(number, approvals)) {
|
|
2147
|
+
await this.clearApplyFailure(number, { kinds: ['gate'], recordedBefore: approvedAfter });
|
|
2148
|
+
}
|
|
1887
2149
|
this.prs.invalidateDetailCache(number);
|
|
1888
2150
|
this.events?.emit({
|
|
1889
2151
|
kind: 'approval-changed',
|
|
@@ -1926,6 +2188,29 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1926
2188
|
return approvals;
|
|
1927
2189
|
}
|
|
1928
2190
|
|
|
2191
|
+
/**
|
|
2192
|
+
* The folder placeholder a file's revert takes along, or null. When a
|
|
2193
|
+
* request removed a folder's last file, the placeholder that keeps the
|
|
2194
|
+
* folder came with it (git may even pair them as one rename); reverting the
|
|
2195
|
+
* file alone would leave that hidden placeholder as a change nobody can
|
|
2196
|
+
* see. So it is reverted too — when the request changed it, and only when
|
|
2197
|
+
* that cannot leave the folder with nothing in git: either the file comes
|
|
2198
|
+
* back, or the base had the placeholder to restore.
|
|
2199
|
+
*/
|
|
2200
|
+
private async placeholderRevertedWith(
|
|
2201
|
+
workspaceId: string,
|
|
2202
|
+
mergeBase: string,
|
|
2203
|
+
repoRelPath: string,
|
|
2204
|
+
changedPaths: string[],
|
|
2205
|
+
): Promise<string | null> {
|
|
2206
|
+
if (isFolderPlaceholder(repoRelPath)) return null;
|
|
2207
|
+
const slash = repoRelPath.lastIndexOf('/');
|
|
2208
|
+
const placeholder = folderPlaceholderPath(slash === -1 ? '' : repoRelPath.slice(0, slash));
|
|
2209
|
+
if (!changedPaths.includes(placeholder)) return null;
|
|
2210
|
+
if (await this.git.pathExistsAtRef(workspaceId, mergeBase, repoRelPath)) return placeholder;
|
|
2211
|
+
return (await this.git.pathExistsAtRef(workspaceId, mergeBase, placeholder)) ? placeholder : null;
|
|
2212
|
+
}
|
|
2213
|
+
|
|
1929
2214
|
/**
|
|
1930
2215
|
* Decline ONE file of an open change request: restore its merge-base
|
|
1931
2216
|
* version on the SOURCE branch (commit + push), so the file drops out of
|
|
@@ -1955,6 +2240,8 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1955
2240
|
}
|
|
1956
2241
|
const baseBranch = summary.base;
|
|
1957
2242
|
const headBranch = summary.branch;
|
|
2243
|
+
// Taken BEFORE the head moves (see `clearApplyFailure`).
|
|
2244
|
+
const headMovedAfter = new Date();
|
|
1958
2245
|
|
|
1959
2246
|
const ws = await this.workspaceService.getOrCreateForBranch(headBranch);
|
|
1960
2247
|
// Best-effort freshen of the source checkout: the diff below reads origin
|
|
@@ -1987,42 +2274,373 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1987
2274
|
throw new WorkflowDomainError('These branches share no history to revert to.', 422);
|
|
1988
2275
|
}
|
|
1989
2276
|
|
|
2277
|
+
const placeholder = await this.placeholderRevertedWith(ws.id, mergeBase, repoRelPath, paths);
|
|
2278
|
+
const reverted = placeholder ? [repoRelPath, placeholder] : [repoRelPath];
|
|
2279
|
+
|
|
1990
2280
|
// Same per-file lock every other editor of this path takes — a concurrent
|
|
1991
2281
|
// save must not race the restore between write and commit.
|
|
1992
|
-
const
|
|
1993
|
-
|
|
1994
|
-
if (!lock.acquired) {
|
|
1995
|
-
throw new WorkflowDomainError(
|
|
1996
|
-
`${repoRelPath} is being edited by ${lock.lock.holderName} — try again once the edit settles.`,
|
|
1997
|
-
409,
|
|
1998
|
-
);
|
|
1999
|
-
}
|
|
2282
|
+
const held: string[] = [];
|
|
2283
|
+
let revertError: { reason: unknown } | null = null;
|
|
2000
2284
|
try {
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
ws.id,
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2285
|
+
for (const p of reverted) {
|
|
2286
|
+
const lockPath = `${this.kbDirName}/${p}`;
|
|
2287
|
+
const lock = await this.fileLocks.acquire(ws.id, headBranch, lockPath, user);
|
|
2288
|
+
if (!lock.acquired) {
|
|
2289
|
+
throw new WorkflowDomainError(
|
|
2290
|
+
p === repoRelPath
|
|
2291
|
+
? `${repoRelPath} is being edited by ${lock.lock.holderName} — try again once the edit settles.`
|
|
2292
|
+
: `The folder of ${repoRelPath} is being changed by ${lock.lock.holderName} — try again once the edit settles.`,
|
|
2293
|
+
409,
|
|
2294
|
+
);
|
|
2295
|
+
}
|
|
2296
|
+
held.push(lockPath);
|
|
2297
|
+
}
|
|
2298
|
+
for (const p of reverted) {
|
|
2299
|
+
await this.git.restorePathFromRef(ws.id, mergeBase, p);
|
|
2300
|
+
await this.git.commitFile(
|
|
2301
|
+
ws.id,
|
|
2302
|
+
user,
|
|
2303
|
+
p,
|
|
2304
|
+
`Revert ${p} (declined in change request #${number})`,
|
|
2305
|
+
true, // skipValidator — this restores an already-validated base version
|
|
2306
|
+
);
|
|
2307
|
+
}
|
|
2009
2308
|
await this.trackedPush(ws.id, user);
|
|
2010
|
-
}
|
|
2011
|
-
|
|
2012
|
-
// a duplicate commit through releaseLock.
|
|
2013
|
-
await this.fileLocks.release(ws.id, headBranch, lockPath, user);
|
|
2309
|
+
} catch (err) {
|
|
2310
|
+
revertError = { reason: err };
|
|
2014
2311
|
}
|
|
2312
|
+
// Committed inline — drop the lock rows directly rather than enqueueing
|
|
2313
|
+
// a duplicate commit through releaseLock. Each is released on its own, on
|
|
2314
|
+
// success and failure alike: one failed release must not leave the other
|
|
2315
|
+
// lock held. The revert's own error wins; otherwise a failed release
|
|
2316
|
+
// still surfaces, as a single release's did.
|
|
2317
|
+
const released = await Promise.allSettled(
|
|
2318
|
+
held.map((lockPath) => this.fileLocks.release(ws.id, headBranch, lockPath, user)),
|
|
2319
|
+
);
|
|
2320
|
+
if (revertError) throw revertError.reason;
|
|
2321
|
+
const failedRelease = released.find((r): r is PromiseRejectedResult => r.status === 'rejected');
|
|
2322
|
+
if (failedRelease) throw failedRelease.reason;
|
|
2323
|
+
// The source head moved: the refusal described a revision that is gone.
|
|
2324
|
+
await this.clearApplyFailure(number, { recordedBefore: headMovedAfter });
|
|
2015
2325
|
this.prs.invalidateDetailCache(number);
|
|
2016
2326
|
|
|
2017
2327
|
const remaining = await this.git.changedPathsForPr(ws.id, baseBranch, headBranch);
|
|
2018
2328
|
if (remaining.length > 0) {
|
|
2019
|
-
|
|
2329
|
+
// Answered like the file list: the placeholder is not a remaining file.
|
|
2330
|
+
return { closed: false, remainingPaths: remaining.filter((p) => !isFolderPlaceholder(p)) };
|
|
2020
2331
|
}
|
|
2021
2332
|
// Every change declined → the request proposes nothing.
|
|
2022
2333
|
await this.closeEmptyChangeRequest(number, user);
|
|
2023
2334
|
return { closed: true, remainingPaths: [] };
|
|
2024
2335
|
}
|
|
2025
2336
|
|
|
2337
|
+
/**
|
|
2338
|
+
* The open change requests that propose files under a KB folder, and
|
|
2339
|
+
* whether the caller may take those files out of each — what a folder
|
|
2340
|
+
* delete asks before it offers "Delete folder and its proposed changes".
|
|
2341
|
+
*
|
|
2342
|
+
* The caller may act on a request they authored, on any request as an
|
|
2343
|
+
* admin, or on a request whose every proposed file under the folder they
|
|
2344
|
+
* may write — per file, not at the folder's top: a subfolder can deny a
|
|
2345
|
+
* writer of its parent. The last two are judged on `origin/<base>`, like
|
|
2346
|
+
* every other request verb. An access answer that cannot be resolved
|
|
2347
|
+
* (null) is a no.
|
|
2348
|
+
*/
|
|
2349
|
+
async changeRequestsUnderFolder(folder: string, user: AuthUser): Promise<FolderChangeRequest[]> {
|
|
2350
|
+
const prefix = folderPrefix(folder);
|
|
2351
|
+
const open = await this.prs.listOpenPrs({ fresh: true });
|
|
2352
|
+
// The list's touched paths are best-effort: a diff it could not read comes
|
|
2353
|
+
// back EMPTY, indistinguishable from a request proposing nothing. This
|
|
2354
|
+
// answer gates a destructive action, so a request listed empty is read
|
|
2355
|
+
// again here, and one whose diff still cannot be read fails the question
|
|
2356
|
+
// rather than dropping out of it.
|
|
2357
|
+
const resolved = await Promise.all(
|
|
2358
|
+
open.map(async (cr) => {
|
|
2359
|
+
if (cr.touchedNodePaths.length > 0) return { cr, touched: cr.touchedNodePaths };
|
|
2360
|
+
try {
|
|
2361
|
+
const ws = await this.workspaceService.getOrCreateForBranch(cr.branch);
|
|
2362
|
+
return { cr, touched: await this.git.changedPathsForPr(ws.id, cr.base, cr.branch) };
|
|
2363
|
+
} catch (err) {
|
|
2364
|
+
log.warn(
|
|
2365
|
+
`changedPathsForPr failed for #${cr.number} under folder ${printable(folder)}: ${printable(sanitizeError(err))}`,
|
|
2366
|
+
);
|
|
2367
|
+
throw new WorkflowDomainError(
|
|
2368
|
+
`Couldn't read what change request #${cr.number} proposes — try again in a moment.`,
|
|
2369
|
+
500,
|
|
2370
|
+
);
|
|
2371
|
+
}
|
|
2372
|
+
}),
|
|
2373
|
+
);
|
|
2374
|
+
const touching = resolved
|
|
2375
|
+
.map(({ cr, touched }) => ({ cr, paths: touched.filter((p) => p.startsWith(prefix)) }))
|
|
2376
|
+
.filter((t) => t.paths.length > 0);
|
|
2377
|
+
if (touching.length === 0) return [];
|
|
2378
|
+
const callerHash = hashEmail(user.email);
|
|
2379
|
+
// One admin answer per base branch; the requests almost always share one.
|
|
2380
|
+
const baseWorkspaces = new Map<string, Promise<string>>();
|
|
2381
|
+
const baseWorkspace = (base: string): Promise<string> => {
|
|
2382
|
+
let ws = baseWorkspaces.get(base);
|
|
2383
|
+
if (!ws) {
|
|
2384
|
+
ws = this.workspaceService.getOrCreateForBranch(base).then((w) => w.id);
|
|
2385
|
+
baseWorkspaces.set(base, ws);
|
|
2386
|
+
}
|
|
2387
|
+
return ws;
|
|
2388
|
+
};
|
|
2389
|
+
const adminByBase = new Map<string, Promise<boolean>>();
|
|
2390
|
+
const adminOn = (base: string): Promise<boolean> => {
|
|
2391
|
+
let admin = adminByBase.get(base);
|
|
2392
|
+
if (!admin) {
|
|
2393
|
+
admin = baseWorkspace(base).then(
|
|
2394
|
+
async (wsId) => (await this.accessControl.canWriteAtRef(wsId, `origin/${base}`, user.email, 'roles.yaml')) === true,
|
|
2395
|
+
);
|
|
2396
|
+
adminByBase.set(base, admin);
|
|
2397
|
+
}
|
|
2398
|
+
return admin;
|
|
2399
|
+
};
|
|
2400
|
+
const writerOfAll = async (base: string, paths: string[]): Promise<boolean> => {
|
|
2401
|
+
const answers = await this.accessControl.canWriteBatchAtRef(
|
|
2402
|
+
await baseWorkspace(base),
|
|
2403
|
+
`origin/${base}`,
|
|
2404
|
+
user.email,
|
|
2405
|
+
paths,
|
|
2406
|
+
);
|
|
2407
|
+
return answers !== null && paths.every((p) => answers.get(p) === true);
|
|
2408
|
+
};
|
|
2409
|
+
|
|
2410
|
+
return Promise.all(
|
|
2411
|
+
touching.map(async ({ cr, paths }): Promise<FolderChangeRequest> => {
|
|
2412
|
+
const mine = !!cr.authorId && cr.authorId === callerHash;
|
|
2413
|
+
const listed = {
|
|
2414
|
+
number: cr.number,
|
|
2415
|
+
title: cr.title,
|
|
2416
|
+
authorName: cr.appAuthor?.name ?? cr.author.name ?? null,
|
|
2417
|
+
mine,
|
|
2418
|
+
paths,
|
|
2419
|
+
};
|
|
2420
|
+
if (mine || (await adminOn(cr.base)) || (await writerOfAll(cr.base, paths))) {
|
|
2421
|
+
return { ...listed, mayRemove: true };
|
|
2422
|
+
}
|
|
2423
|
+
return {
|
|
2424
|
+
...listed,
|
|
2425
|
+
mayRemove: false,
|
|
2426
|
+
reason: `#${cr.number} was proposed by ${cr.appAuthor?.name ?? 'someone else'}; only its author, an admin or someone who can write every file it proposes here can change it.`,
|
|
2427
|
+
};
|
|
2428
|
+
}),
|
|
2429
|
+
);
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
/**
|
|
2433
|
+
* Take every file under `folder` out of every open change request that
|
|
2434
|
+
* proposes one — the request half of "Delete folder and its proposed
|
|
2435
|
+
* changes". All-or-nothing on permission: when the caller may not act on
|
|
2436
|
+
* even one of the requests, nothing is touched. A request left proposing
|
|
2437
|
+
* nothing is withdrawn (closed, its branch retired), exactly as declining
|
|
2438
|
+
* its last file would.
|
|
2439
|
+
*/
|
|
2440
|
+
async removeFolderFromChangeRequests(
|
|
2441
|
+
folder: string,
|
|
2442
|
+
user: AuthUser,
|
|
2443
|
+
): Promise<FolderChangeRequestRemoval[]> {
|
|
2444
|
+
const requests = await this.changeRequestsUnderFolder(folder, user);
|
|
2445
|
+
const refused = requests.filter((r) => !r.mayRemove);
|
|
2446
|
+
if (refused.length > 0) {
|
|
2447
|
+
throw new WorkflowDomainError(
|
|
2448
|
+
`You can't remove proposed changes from ${refused.map((r) => `#${r.number}`).join(', ')} — only the author, an admin or someone who can write every file it proposes here can.`,
|
|
2449
|
+
403,
|
|
2450
|
+
);
|
|
2451
|
+
}
|
|
2452
|
+
const prefix = folderPrefix(folder);
|
|
2453
|
+
|
|
2454
|
+
// Everything that can refuse happens before the first commit, so a
|
|
2455
|
+
// refusal leaves every request as it was: each request's source checkout
|
|
2456
|
+
// brought up to date (a failed pull stops here — restoring into a stale
|
|
2457
|
+
// or conflicted tree could commit the wrong content), its paths read as
|
|
2458
|
+
// they are NOW rather than as the list saw them, its merge base found,
|
|
2459
|
+
// and every file's lock held.
|
|
2460
|
+
const plans: { number: number; branch: string; wsId: string; paths: string[]; mergeBase: string | null }[] = [];
|
|
2461
|
+
for (const request of requests) {
|
|
2462
|
+
const summary = await this.prs.getPr(request.number);
|
|
2463
|
+
if (!summary || summary.state !== 'open') continue;
|
|
2464
|
+
const ws = await this.workspaceService.getOrCreateForBranch(summary.branch);
|
|
2465
|
+
try {
|
|
2466
|
+
await this.pullWorkspace(ws.id);
|
|
2467
|
+
} catch (err) {
|
|
2468
|
+
log.warn(
|
|
2469
|
+
`pull failed for #${summary.number} before removing folder ${printable(folder)}: ${printable(sanitizeError(err))}`,
|
|
2470
|
+
);
|
|
2471
|
+
throw new WorkflowDomainError(
|
|
2472
|
+
`#${summary.number} could not be brought up to date, so nothing was removed — try again in a moment.`,
|
|
2473
|
+
409,
|
|
2474
|
+
);
|
|
2475
|
+
}
|
|
2476
|
+
const paths = (await this.git.changedPathsForPr(ws.id, summary.base, summary.branch)).filter((p) =>
|
|
2477
|
+
p.startsWith(prefix),
|
|
2478
|
+
);
|
|
2479
|
+
// The permission above was judged on the paths the request proposed
|
|
2480
|
+
// then. One proposed since was never judged — a writer of every earlier
|
|
2481
|
+
// file need not be one of it — so the request has to be asked about again.
|
|
2482
|
+
const unjudged = paths.filter((p) => !request.paths.includes(p));
|
|
2483
|
+
if (unjudged.length > 0) {
|
|
2484
|
+
throw new WorkflowDomainError(
|
|
2485
|
+
`#${summary.number} changed while the folder was being deleted (${unjudged.length === 1 ? unjudged[0] :`${unjudged.length} new files`}), so no proposed changes were removed — try again.`,
|
|
2486
|
+
409,
|
|
2487
|
+
);
|
|
2488
|
+
}
|
|
2489
|
+
let mergeBase: string | null = null;
|
|
2490
|
+
if (paths.length > 0) {
|
|
2491
|
+
mergeBase = await this.git.mergeBaseForPr(ws.id, summary.base, summary.branch);
|
|
2492
|
+
if (!mergeBase) {
|
|
2493
|
+
throw new WorkflowDomainError(`#${summary.number} shares no history with its target to revert to.`, 422);
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
plans.push({ number: summary.number, branch: summary.branch, wsId: ws.id, paths, mergeBase });
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
// Requests can share a source branch, and so a workspace and even a file.
|
|
2500
|
+
// The work is grouped per workspace: each file is locked, restored and
|
|
2501
|
+
// committed once, and each branch pushed once. One restore can only undo a
|
|
2502
|
+
// shared file for requests that agree on what it reverts TO — requests on
|
|
2503
|
+
// one branch aimed at targets with different merge bases do not, and one
|
|
2504
|
+
// of them would be reported emptied while still proposing the file. That
|
|
2505
|
+
// combination is refused here, before anything is locked or changed.
|
|
2506
|
+
const byWorkspace = new Map<
|
|
2507
|
+
string,
|
|
2508
|
+
{ wsId: string; branch: string; plans: typeof plans; paths: { path: string; mergeBase: string }[] }
|
|
2509
|
+
>();
|
|
2510
|
+
for (const plan of plans) {
|
|
2511
|
+
let group = byWorkspace.get(plan.wsId);
|
|
2512
|
+
if (!group) {
|
|
2513
|
+
group = { wsId: plan.wsId, branch: plan.branch, plans: [], paths: [] };
|
|
2514
|
+
byWorkspace.set(plan.wsId, group);
|
|
2515
|
+
}
|
|
2516
|
+
group.plans.push(plan);
|
|
2517
|
+
for (const repoRelPath of plan.paths) {
|
|
2518
|
+
const seen = group.paths.find((p) => p.path === repoRelPath);
|
|
2519
|
+
if (!seen) {
|
|
2520
|
+
group.paths.push({ path: repoRelPath, mergeBase: plan.mergeBase! });
|
|
2521
|
+
} else if (seen.mergeBase !== plan.mergeBase) {
|
|
2522
|
+
const sharing = group.plans.map((other) => `#${other.number}`).join(', ');
|
|
2523
|
+
throw new WorkflowDomainError(
|
|
2524
|
+
`${sharing} propose ${repoRelPath} from the same branch against different targets, so it can't be taken out of both at once. Decline it in each request instead.`,
|
|
2525
|
+
422,
|
|
2526
|
+
);
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
const groups = [...byWorkspace.values()];
|
|
2531
|
+
const toChange = groups.filter((g) => g.paths.length > 0);
|
|
2532
|
+
|
|
2533
|
+
const held: { wsId: string; branch: string; lockPath: string }[] = [];
|
|
2534
|
+
// Every held lock is let go, each on its own: one failed release must not
|
|
2535
|
+
// keep the rest held until they expire.
|
|
2536
|
+
const releaseAll = async () => {
|
|
2537
|
+
for (const h of held.splice(0)) {
|
|
2538
|
+
try {
|
|
2539
|
+
await this.fileLocks.release(h.wsId, h.branch, h.lockPath, user);
|
|
2540
|
+
} catch (err) {
|
|
2541
|
+
log.warn(`lock release failed for ${printable(h.lockPath)}: ${printable(sanitizeError(err))}`);
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
};
|
|
2545
|
+
const pushed: typeof toChange = [];
|
|
2546
|
+
let pushFailure: { group: (typeof toChange)[number]; err: unknown } | null = null;
|
|
2547
|
+
try {
|
|
2548
|
+
for (const group of toChange) {
|
|
2549
|
+
for (const { path: repoRelPath } of group.paths) {
|
|
2550
|
+
const lockPath = `${this.kbDirName}/${repoRelPath}`;
|
|
2551
|
+
const lock = await this.fileLocks.acquire(group.wsId, group.branch, lockPath, user);
|
|
2552
|
+
if (!lock.acquired) {
|
|
2553
|
+
throw new WorkflowDomainError(
|
|
2554
|
+
`${repoRelPath} is being edited by ${lock.lock.holderName} — try again once the edit settles.`,
|
|
2555
|
+
409,
|
|
2556
|
+
);
|
|
2557
|
+
}
|
|
2558
|
+
held.push({ wsId: group.wsId, branch: group.branch, lockPath });
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
// The git half is ONE operation under one reservation of every checkout
|
|
2562
|
+
// involved: it records each HEAD, commits every revert before pushing
|
|
2563
|
+
// anything, pushes branch by branch, and undoes what didn't publish (see
|
|
2564
|
+
// `GitService.revertPathsAndPush`). A failed restore or commit throws
|
|
2565
|
+
// with every request left as it was. Pushes can't be made atomic, so a
|
|
2566
|
+
// failed push leaves the requests split into "done" and "untouched", and
|
|
2567
|
+
// running the action again finishes the rest.
|
|
2568
|
+
if (toChange.length > 0) {
|
|
2569
|
+
const outcome = await this.git.revertPathsAndPush(
|
|
2570
|
+
user,
|
|
2571
|
+
toChange.map((group) => {
|
|
2572
|
+
const numbers = group.plans.map((plan) => `#${plan.number}`).join(', ');
|
|
2573
|
+
return {
|
|
2574
|
+
workspaceId: group.wsId,
|
|
2575
|
+
paths: group.paths.map(({ path: repoRelPath, mergeBase }) => ({
|
|
2576
|
+
path: repoRelPath,
|
|
2577
|
+
ref: mergeBase,
|
|
2578
|
+
subject: commitSubject(
|
|
2579
|
+
`Revert ${repoRelPath} (folder deleted; removed from change request ${numbers})`,
|
|
2580
|
+
`Revert ${basenameOf(repoRelPath)} (folder deleted; removed from ${group.plans.length === 1 ? `change request ${numbers}` : `${group.plans.length} change requests`})`,
|
|
2581
|
+
),
|
|
2582
|
+
undoSubject: commitSubject(
|
|
2583
|
+
`Undo revert of ${repoRelPath} (folder removal stopped)`,
|
|
2584
|
+
`Undo revert of ${basenameOf(repoRelPath)} (folder removal stopped)`,
|
|
2585
|
+
),
|
|
2586
|
+
})),
|
|
2587
|
+
};
|
|
2588
|
+
}),
|
|
2589
|
+
);
|
|
2590
|
+
for (const group of toChange) {
|
|
2591
|
+
if (outcome.pushed.includes(group.wsId)) {
|
|
2592
|
+
this.noteGitSyncOk(group.wsId, group.branch);
|
|
2593
|
+
pushed.push(group);
|
|
2594
|
+
for (const plan of group.plans) this.prs.invalidateDetailCache(plan.number);
|
|
2595
|
+
} else if (outcome.failed?.workspaceId === group.wsId) {
|
|
2596
|
+
this.noteGitSyncFailed(group.wsId, group.branch, outcome.failed.error);
|
|
2597
|
+
pushFailure = { group, err: outcome.failed.error };
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
} finally {
|
|
2602
|
+
await releaseAll();
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
const finished = (pushFailure ? pushed : groups).flatMap((group) => group.plans);
|
|
2606
|
+
if (pushFailure && finished.length === 0) throw pushFailure.err;
|
|
2607
|
+
const results: FolderChangeRequestRemoval[] = [];
|
|
2608
|
+
for (const plan of finished) {
|
|
2609
|
+
const withdrawn = await this.closeEmptyChangeRequest(plan.number, user);
|
|
2610
|
+
// A request still open is read once more, to say why: a file under the
|
|
2611
|
+
// folder proposed while this ran (never judged, so left alone), or —
|
|
2612
|
+
// proposing nothing at all — a save still landing kept it open.
|
|
2613
|
+
let stillProposed: string[] = [];
|
|
2614
|
+
let keptForSaves = false;
|
|
2615
|
+
if (!withdrawn) {
|
|
2616
|
+
try {
|
|
2617
|
+
const summary = await this.prs.getPr(plan.number);
|
|
2618
|
+
if (summary?.state === 'open') {
|
|
2619
|
+
const now = await this.git.changedPathsForPr(plan.wsId, summary.base, summary.branch);
|
|
2620
|
+
stillProposed = now.filter((p) => p.startsWith(prefix) && !isFolderPlaceholder(p));
|
|
2621
|
+
keptForSaves = now.length === 0;
|
|
2622
|
+
}
|
|
2623
|
+
} catch (err) {
|
|
2624
|
+
log.warn(
|
|
2625
|
+
`could not re-read #${plan.number} after removing folder ${printable(folder)}: ${printable(sanitizeError(err))}`,
|
|
2626
|
+
);
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
results.push({ number: plan.number, removedPaths: plan.paths, withdrawn, stillProposed, keptForSaves });
|
|
2630
|
+
}
|
|
2631
|
+
if (pushFailure) {
|
|
2632
|
+
const failed = pushFailure.group.plans.map((plan) => `#${plan.number}`).join(', ');
|
|
2633
|
+
log.warn(
|
|
2634
|
+
`push failed for ${failed} removing folder ${printable(folder)}: ${printable(sanitizeError(pushFailure.err))}`,
|
|
2635
|
+
);
|
|
2636
|
+
throw new WorkflowDomainError(
|
|
2637
|
+
`The folder's files were removed from ${finished.map((plan) => `#${plan.number}`).join(', ')}, but ${failed} could not be updated and it and any later requests were left as they were. Run the delete again to finish.`,
|
|
2638
|
+
500,
|
|
2639
|
+
);
|
|
2640
|
+
}
|
|
2641
|
+
return results;
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2026
2644
|
/**
|
|
2027
2645
|
* DELETE a change request outright: close it (whatever its diff says) and
|
|
2028
2646
|
* retire its source branch — the request, its proposal, and the branch that
|
|
@@ -2126,23 +2744,29 @@ export class WorkflowService implements IWorkflowService {
|
|
|
2126
2744
|
* is AUTHORITATIVE — recomputed, and a diff failure aborts rather than
|
|
2127
2745
|
* closes, so a transient git error can never eat a live request.
|
|
2128
2746
|
*
|
|
2747
|
+
* An empty diff is not yet an empty request while a save to its branch is
|
|
2748
|
+
* still landing: one under a held lock or queued for the commit worker is
|
|
2749
|
+
* on disk but not in the diff. Retiring the branch deletes its checkout and
|
|
2750
|
+
* that save with it, so such a request stays open — the save commits and it
|
|
2751
|
+
* proposes something again, or a later look closes it.
|
|
2752
|
+
*
|
|
2129
2753
|
* Returns true when THIS call closed it.
|
|
2130
2754
|
*/
|
|
2131
2755
|
async closeEmptyChangeRequest(number: number, user: AuthUser): Promise<boolean> {
|
|
2132
2756
|
const summary = await this.prs.getPr(number);
|
|
2133
2757
|
if (!summary || summary.state !== 'open') return false;
|
|
2134
2758
|
let paths: string[];
|
|
2759
|
+
let wsId: string;
|
|
2135
2760
|
try {
|
|
2136
2761
|
const ws = await this.workspaceService.getOrCreateForBranch(summary.branch);
|
|
2762
|
+
wsId = ws.id;
|
|
2137
2763
|
paths = await this.git.changedPathsForPr(ws.id, summary.base, summary.branch);
|
|
2138
2764
|
} catch (err) {
|
|
2139
|
-
|
|
2140
|
-
`[cr] empty-check for change request #${number} failed — leaving it open:`,
|
|
2141
|
-
err,
|
|
2142
|
-
);
|
|
2765
|
+
crLog.warn(`empty-check for change request #${number} failed — leaving it open:`, { err });
|
|
2143
2766
|
return false;
|
|
2144
2767
|
}
|
|
2145
2768
|
if (paths.length > 0) return false;
|
|
2769
|
+
if (await this.savesInFlight(wsId)) return false;
|
|
2146
2770
|
|
|
2147
2771
|
// Guard on `state = 'open'` so a concurrent merge or withdraw wins the
|
|
2148
2772
|
// race and this becomes a no-op.
|
|
@@ -2159,6 +2783,21 @@ export class WorkflowService implements IWorkflowService {
|
|
|
2159
2783
|
return true;
|
|
2160
2784
|
}
|
|
2161
2785
|
|
|
2786
|
+
/**
|
|
2787
|
+
* Whether a save to this branch's checkout is still landing: a live file
|
|
2788
|
+
* lock (the write is mid-flight) or any queued commit, stuck ones included
|
|
2789
|
+
* (their file is still only on disk). An answer that cannot be read is a
|
|
2790
|
+
* yes — the caller is about to delete the checkout.
|
|
2791
|
+
*/
|
|
2792
|
+
private async savesInFlight(wsId: string): Promise<boolean> {
|
|
2793
|
+
try {
|
|
2794
|
+
return (await this.fileLocks.hasAnyActive(wsId)) || (await this.pendingCommits.hasAnyForWorkspace(wsId));
|
|
2795
|
+
} catch (err) {
|
|
2796
|
+
crLog.warn(`could not tell whether saves are landing on ${printable(wsId)} — keeping its request open:`, { err });
|
|
2797
|
+
return true;
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2162
2801
|
/**
|
|
2163
2802
|
* Close every open change request either of whose branches no longer exists.
|
|
2164
2803
|
*
|
|
@@ -2222,13 +2861,13 @@ export class WorkflowService implements IWorkflowService {
|
|
|
2222
2861
|
});
|
|
2223
2862
|
live = new Set(branches.map((b) => b.name));
|
|
2224
2863
|
} catch (err) {
|
|
2225
|
-
|
|
2864
|
+
crLog.warn('branch sweep skipped — could not list branches:', { err });
|
|
2226
2865
|
return 0;
|
|
2227
2866
|
}
|
|
2228
2867
|
// An empty branch list means something is wrong with the clone, not that
|
|
2229
2868
|
// every branch in the repo was deleted at once. Refuse to act on it.
|
|
2230
2869
|
if (live.size === 0) {
|
|
2231
|
-
|
|
2870
|
+
crLog.warn('branch sweep skipped — branch list came back empty');
|
|
2232
2871
|
return 0;
|
|
2233
2872
|
}
|
|
2234
2873
|
|
|
@@ -2254,9 +2893,7 @@ export class WorkflowService implements IWorkflowService {
|
|
|
2254
2893
|
if (updated.length === 0) continue;
|
|
2255
2894
|
this.prs.invalidateDetailCache(cr.number);
|
|
2256
2895
|
this.events?.emit({ kind: 'change-request-rejected', number: cr.number });
|
|
2257
|
-
|
|
2258
|
-
`[cr] closed change request #${cr.number}: branch "${missing}" no longer exists`,
|
|
2259
|
-
);
|
|
2896
|
+
crLog.info(`closed change request #${cr.number}: branch "${missing}" no longer exists`);
|
|
2260
2897
|
closed++;
|
|
2261
2898
|
}
|
|
2262
2899
|
return closed;
|
|
@@ -2282,12 +2919,10 @@ export class WorkflowService implements IWorkflowService {
|
|
|
2282
2919
|
this.sweepKick = this.closeChangeRequestsWithDeletedBranches()
|
|
2283
2920
|
.then((n) => {
|
|
2284
2921
|
if (n > 0) {
|
|
2285
|
-
|
|
2286
|
-
`[cr] on-demand sweep closed ${n} stranded change request${n === 1 ? '' : 's'}`,
|
|
2287
|
-
);
|
|
2922
|
+
crLog.info(`on-demand sweep closed ${n} stranded change request${n === 1 ? '' : 's'}`);
|
|
2288
2923
|
}
|
|
2289
2924
|
})
|
|
2290
|
-
.catch((err) =>
|
|
2925
|
+
.catch((err) => crLog.warn('on-demand deleted-branch sweep failed:', { err }))
|
|
2291
2926
|
.finally(() => {
|
|
2292
2927
|
this.sweepKick = null;
|
|
2293
2928
|
});
|
|
@@ -2412,7 +3047,7 @@ export class WorkflowService implements IWorkflowService {
|
|
|
2412
3047
|
// roles.yaml differs from the base branch's, we restore the base version on
|
|
2413
3048
|
// the source branch (commit + push) BEFORE merging, so the merged diff
|
|
2414
3049
|
// carries no roles.yaml change. roles.yaml is mutable ONLY via the admin
|
|
2415
|
-
//
|
|
3050
|
+
// App roles surface (itself admin-gated). The rest of the CR merges
|
|
2416
3051
|
// normally. Best-effort by design is NOT acceptable here — a failure to
|
|
2417
3052
|
// neutralise must abort the merge, never fall through.
|
|
2418
3053
|
const preserved = await this.preserveBaseRolesYaml(number, user, baseBranch);
|
|
@@ -2462,17 +3097,31 @@ export class WorkflowService implements IWorkflowService {
|
|
|
2462
3097
|
}
|
|
2463
3098
|
throw err;
|
|
2464
3099
|
}
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
//
|
|
2469
|
-
//
|
|
2470
|
-
|
|
3100
|
+
await this.pullMergeTarget(baseBranch, user);
|
|
3101
|
+
this.prs.invalidateDetailCache(number);
|
|
3102
|
+
this.events?.emit({ kind: 'change-request-merged', number });
|
|
3103
|
+
// AFTER the event: the applying UI is waiting on `change-request-merged`,
|
|
3104
|
+
// and branch retirement is git IO it must never wait behind.
|
|
3105
|
+
await this.retireMergedSourceBranch(number, baseBranch, user);
|
|
3106
|
+
return { kind: 'merged', result };
|
|
3107
|
+
}
|
|
3108
|
+
|
|
3109
|
+
/**
|
|
3110
|
+
* The merge landed on `origin/<targetBranch>`. Pull the TARGET branch's own
|
|
3111
|
+
* workspace so its working tree doesn't fall behind the remote — the file
|
|
3112
|
+
* tools serve the working tree (not origin), so without this a read right
|
|
3113
|
+
* after a merge misses the just-merged change. Best-effort: the merge already
|
|
3114
|
+
* succeeded on origin, so a pull hiccup must not fail the response (a later
|
|
3115
|
+
* fetch/pull reconciles).
|
|
3116
|
+
*/
|
|
3117
|
+
private async pullMergeTarget(targetBranch: string, user: AuthUser): Promise<void> {
|
|
2471
3118
|
let targetWorkspaceId: string | undefined;
|
|
3119
|
+
/** Whether the pull below already announced the new tree. See the emit after it. */
|
|
3120
|
+
let announced = false;
|
|
2472
3121
|
try {
|
|
2473
|
-
const targetWorkspace = await this.workspaceService.getOrCreateForBranch(
|
|
3122
|
+
const targetWorkspace = await this.workspaceService.getOrCreateForBranch(targetBranch);
|
|
2474
3123
|
targetWorkspaceId = targetWorkspace.id;
|
|
2475
|
-
await this.pullWorkspace(targetWorkspace.id);
|
|
3124
|
+
announced = await this.pullWorkspace(targetWorkspace.id);
|
|
2476
3125
|
} catch (err) {
|
|
2477
3126
|
// Still best-effort for the merge response (the merge already landed
|
|
2478
3127
|
// on origin) — but a rebase CONFLICT here means the target workspace
|
|
@@ -2483,17 +3132,289 @@ export class WorkflowService implements IWorkflowService {
|
|
|
2483
3132
|
if (err instanceof PullRebaseConflictError && targetWorkspaceId) {
|
|
2484
3133
|
await this.queuePullConflictRecovery(targetWorkspaceId, err, user);
|
|
2485
3134
|
}
|
|
2486
|
-
|
|
2487
|
-
`[merge] post-merge pull of target "${baseBranch}" failed — its workspace may be momentarily behind origin`,
|
|
3135
|
+
mergeLog.warn(`post-merge pull of target "${targetBranch}" failed — its workspace may be momentarily behind origin`, {
|
|
2488
3136
|
err,
|
|
2489
|
-
);
|
|
3137
|
+
});
|
|
2490
3138
|
}
|
|
3139
|
+
// THE MERGE ITSELF CHANGED THE TREE, and the pull above almost never says
|
|
3140
|
+
// so. Both callers — applying a change request, and an agent merging a
|
|
3141
|
+
// branch — run `git merge --no-ff` in this very workspace and push the
|
|
3142
|
+
// result, so by the time we pull, the clone is already at origin: the pull
|
|
3143
|
+
// reports "up to date", `treeChanged` is false, and nothing is announced —
|
|
3144
|
+
// while the working tree holds a `.tool` or a `SKILL.md` that was not
|
|
3145
|
+
// there a second ago.
|
|
3146
|
+
//
|
|
3147
|
+
// Nothing else covers it. `change-request-merged` carries no branch and no
|
|
3148
|
+
// workspace, so the catalog subscriber cannot act on it; a later
|
|
3149
|
+
// `POST /api/sync/<branch>` answers "up-to-date" without pulling, for the
|
|
3150
|
+
// same reason. So an approved tool stayed invisible — to every open MCP
|
|
3151
|
+
// connection and every browser — until some unrelated write to the default
|
|
3152
|
+
// branch happened to drop the caches, or the catalogs' own TTL ran out.
|
|
3153
|
+
// That is the merge path of "whichever path made the commit".
|
|
3154
|
+
//
|
|
3155
|
+
// Emitted only when the pull did not already: on the rare path where the
|
|
3156
|
+
// clone WAS behind (someone else pushed between the merge and the pull),
|
|
3157
|
+
// the pull announced the same tree and a second event buys a duplicate
|
|
3158
|
+
// re-scan and a duplicate browser refetch.
|
|
3159
|
+
//
|
|
3160
|
+
// A pull that FAILED is still announced, and that is not a guess about a
|
|
3161
|
+
// tree nobody reconciled. The merge ran in THIS workspace — the target
|
|
3162
|
+
// branch's own, resolved above — before pushing, so the merged bytes were
|
|
3163
|
+
// on that disk before the pull was attempted, and a pull that could not
|
|
3164
|
+
// run cannot un-merge them. (For a change request, `targetBranch` is the
|
|
3165
|
+
// branch the merge acted on: `preserveBaseRolesYaml` refuses the merge
|
|
3166
|
+
// outright if it disagrees with the request's own base.) The failure the
|
|
3167
|
+
// pull reports is about OTHER people's commits not arriving, which leaves
|
|
3168
|
+
// the catalogs no staler than they were.
|
|
3169
|
+
if (!announced && targetWorkspaceId && targetBranch === DEFAULT_BRANCH) {
|
|
3170
|
+
this.events?.emit({ kind: 'fs-tree-changed', workspaceId: targetWorkspaceId, branch: DEFAULT_BRANCH });
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
async mergeBranch(user: AuthUser, sourceBranch: string, targetBranch: string): Promise<MergeBranchOutcome> {
|
|
3175
|
+
if (sourceBranch === targetBranch) {
|
|
3176
|
+
throw new WorkflowValidationError(`\`source\` and \`target\` are both "${sourceBranch}" — name two different branches.`);
|
|
3177
|
+
}
|
|
3178
|
+
|
|
3179
|
+
// Under the lifecycle lock of BOTH branches, the same protocol
|
|
3180
|
+
// `openChangeRequest` follows — and for the same window. That method holds
|
|
3181
|
+
// these keys across its whole check-then-insert (an auto-merge and a push,
|
|
3182
|
+
// seconds of it), so the "no open request" answer below is only sound
|
|
3183
|
+
// while they are held: a bare read would let a person open the request
|
|
3184
|
+
// mid-merge and the agent would land its content anyway, which is the one
|
|
3185
|
+
// thing this tool exists to prevent. `runOnBranchPair` drops a protected
|
|
3186
|
+
// target's key; the source key is held either way, and every competing
|
|
3187
|
+
// request from this source takes it too.
|
|
3188
|
+
return this.runOnBranchPair(sourceBranch, targetBranch, async () => {
|
|
3189
|
+
// A person merges a change request. Only the request's OWN direction is
|
|
3190
|
+
// blocked: the target merged into the source is how a draft under review
|
|
3191
|
+
// stays current, and that must keep working while the request is open.
|
|
3192
|
+
const [open] = await this.db
|
|
3193
|
+
.select({ number: changeRequests.number })
|
|
3194
|
+
.from(changeRequests)
|
|
3195
|
+
.where(
|
|
3196
|
+
and(
|
|
3197
|
+
eq(changeRequests.sourceBranch, sourceBranch),
|
|
3198
|
+
eq(changeRequests.targetBranch, targetBranch),
|
|
3199
|
+
eq(changeRequests.state, 'open'),
|
|
3200
|
+
),
|
|
3201
|
+
)
|
|
3202
|
+
.limit(1);
|
|
3203
|
+
if (open) throw new OpenChangeRequestBlocksMergeError(sourceBranch, targetBranch, open.number);
|
|
3204
|
+
|
|
3205
|
+
// The merge runs in the target's own workspace, like a change request's.
|
|
3206
|
+
const targetWorkspaceId = (await this.workspaceService.getOrCreateForBranch(targetBranch)).id;
|
|
3207
|
+
if (!(await this.git.remoteBranchExists(targetWorkspaceId, sourceBranch))) {
|
|
3208
|
+
throw new WorkflowValidationError(`No branch named "${sourceBranch}" on the shared remote.`);
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
const result = await this.git.mergeChangeRequest(
|
|
3212
|
+
targetWorkspaceId,
|
|
3213
|
+
sourceBranch,
|
|
3214
|
+
targetBranch,
|
|
3215
|
+
{
|
|
3216
|
+
subject: `Merge ${sourceBranch} into ${targetBranch}`,
|
|
3217
|
+
body: `Merged via Bevel by ${user.name} <${user.email}>`,
|
|
3218
|
+
},
|
|
3219
|
+
user,
|
|
3220
|
+
{
|
|
3221
|
+
// The merge resets that workspace to the published target tip, which
|
|
3222
|
+
// would throw away edits still on their way out. Refuse until they
|
|
3223
|
+
// are shared — asked inside the merge's own reservation, so a save
|
|
3224
|
+
// cannot land between the question and the reset.
|
|
3225
|
+
requireCleanTarget: true,
|
|
3226
|
+
// Into a protected branch, a merge is a commit of every file it
|
|
3227
|
+
// changes: allowed only for a caller who could commit each of them
|
|
3228
|
+
// directly (the same at-HEAD rule the write lock applies). The hook
|
|
3229
|
+
// runs inside that reservation too, against the target tip this
|
|
3230
|
+
// merge is built on rather than a workspace HEAD that may be behind
|
|
3231
|
+
// it — so the rules read are the ones the merge is about to change.
|
|
3232
|
+
//
|
|
3233
|
+
// `changedPaths` is the authorizing set: roles.yaml kept (unlike a
|
|
3234
|
+
// change request's merge, nothing strips it here — so its presence
|
|
3235
|
+
// is what the hook refuses on) and both sides of a rename, since the
|
|
3236
|
+
// old name is a file this merge deletes.
|
|
3237
|
+
authorize: isProtectedBranch(targetBranch)
|
|
3238
|
+
? async ({ sha, changedPaths }) => {
|
|
3239
|
+
if (changedPaths.length === 0) return;
|
|
3240
|
+
// roles.yaml never changes through a merge. A change request's
|
|
3241
|
+
// merge restores the target's copy on the source first
|
|
3242
|
+
// (`preserveBaseRolesYaml`): a draft is a free-for-all, and the
|
|
3243
|
+
// merge is what lands its content on a protected branch. This
|
|
3244
|
+
// path lands draft content the same way, so it refuses rather
|
|
3245
|
+
// than authorizing — even an admin, who could write the file
|
|
3246
|
+
// directly, gets the app's roles surface for that, where the
|
|
3247
|
+
// file is validated (parsable, groups that exist); the agent's
|
|
3248
|
+
// own write tools refuse it under the same guard. A merged
|
|
3249
|
+
// roles.yaml would bypass both, on the one file the access
|
|
3250
|
+
// model cannot survive being wrong.
|
|
3251
|
+
if (changedPaths.includes('roles.yaml')) {
|
|
3252
|
+
throw new WorkflowDomainError(
|
|
3253
|
+
`"${targetBranch}" is protected and this merge would change its roles.yaml. ` +
|
|
3254
|
+
`Roles never change through a merge: restore roles.yaml on "${sourceBranch}" to the version on ` +
|
|
3255
|
+
`"${targetBranch}" and merge again, and ask the user to change roles in the app.`,
|
|
3256
|
+
403,
|
|
3257
|
+
{ kind: 'protected-merge-changes-roles', targetBranch, sourceBranch },
|
|
3258
|
+
);
|
|
3259
|
+
}
|
|
3260
|
+
const allowed = await this.accessControl.canWriteBatchAtRef(
|
|
3261
|
+
targetWorkspaceId,
|
|
3262
|
+
sha,
|
|
3263
|
+
user.email,
|
|
3264
|
+
changedPaths,
|
|
3265
|
+
);
|
|
3266
|
+
const denied = allowed ? changedPaths.filter((p) => !allowed.get(p)) : [];
|
|
3267
|
+
if (denied.length === 0) return;
|
|
3268
|
+
throw new WorkflowDomainError(
|
|
3269
|
+
`"${targetBranch}" is protected and you cannot commit directly to it: ` +
|
|
3270
|
+
`${denied.length} changed file(s) are outside your write access (${denied.join(', ')}). ` +
|
|
3271
|
+
`Open a change request from "${sourceBranch}" into "${targetBranch}" and ask the user to review it in the app.`,
|
|
3272
|
+
403,
|
|
3273
|
+
{ kind: 'protected-merge-target', targetBranch, deniedPaths: denied },
|
|
3274
|
+
);
|
|
3275
|
+
}
|
|
3276
|
+
: undefined,
|
|
3277
|
+
},
|
|
3278
|
+
);
|
|
3279
|
+
if (result.kind === 'conflicts') {
|
|
3280
|
+
return { kind: 'conflicts-need-resolution', conflictedPaths: result.paths };
|
|
3281
|
+
}
|
|
3282
|
+
await this.pullMergeTarget(targetBranch, user);
|
|
3283
|
+
return { kind: 'merged', sha: result.sha };
|
|
3284
|
+
});
|
|
3285
|
+
}
|
|
3286
|
+
|
|
3287
|
+
/**
|
|
3288
|
+
* Start an apply attempt on `number` and return its token. Only the latest
|
|
3289
|
+
* attempt may record a refusal: when two people apply the same request at
|
|
3290
|
+
* once, an older attempt that finishes last must not overwrite the newer
|
|
3291
|
+
* one's verdict. In-process, like the detail and list caches this reads with.
|
|
3292
|
+
* Every attempt must be ended with `endApplyAttempt`.
|
|
3293
|
+
*/
|
|
3294
|
+
beginApplyAttempt(number: number): number {
|
|
3295
|
+
const attempt = ++this.applyAttemptSeq;
|
|
3296
|
+
this.applyAttempts.set(number, attempt);
|
|
3297
|
+
return attempt;
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
/**
|
|
3301
|
+
* An attempt finished. Its entry leaves only if no newer attempt replaced it;
|
|
3302
|
+
* an older attempt ending later then finds no entry and records nothing,
|
|
3303
|
+
* because its token can never match one issued again.
|
|
3304
|
+
*/
|
|
3305
|
+
endApplyAttempt(number: number, attempt: number): void {
|
|
3306
|
+
if (this.applyAttempts.get(number) === attempt) this.applyAttempts.delete(number);
|
|
3307
|
+
}
|
|
3308
|
+
|
|
3309
|
+
/**
|
|
3310
|
+
* Persist why an apply did not land and tell every session. The apply route
|
|
3311
|
+
* already answers the clicker directly (user-scoped `merge-failed`); this is
|
|
3312
|
+
* for everyone else who can see the still-open request — above all its
|
|
3313
|
+
* author, who otherwise sees it pending forever with no word of the refusal.
|
|
3314
|
+
* The event carries only the number: the reason is read back through the
|
|
3315
|
+
* list and detail endpoints, so a session that misses the event gets the
|
|
3316
|
+
* same answer on its next fetch.
|
|
3317
|
+
*
|
|
3318
|
+
* Returns false, writing and announcing nothing, when a newer attempt has
|
|
3319
|
+
* started since `attempt` or the request is no longer open (a concurrent
|
|
3320
|
+
* apply landed it) — a refusal nobody can act on must not reach anyone.
|
|
3321
|
+
*/
|
|
3322
|
+
async recordApplyFailure(
|
|
3323
|
+
number: number,
|
|
3324
|
+
failure: { reason: string; kind: ChangeRequestApplyFailureKind; at?: Date },
|
|
3325
|
+
user: AuthUser,
|
|
3326
|
+
attempt: number,
|
|
3327
|
+
): Promise<boolean> {
|
|
3328
|
+
if (this.applyAttempts.get(number) !== attempt) return false;
|
|
3329
|
+
const at = failure.at ?? new Date();
|
|
3330
|
+
const updated = await this.db
|
|
3331
|
+
.update(changeRequests)
|
|
3332
|
+
.set({
|
|
3333
|
+
applyFailureReason: sanitizeError(failure.reason, { maxLen: APPLY_FAILURE_MAX_LEN }),
|
|
3334
|
+
applyFailureConflicts: failure.kind === 'conflicts',
|
|
3335
|
+
applyFailureKind: failure.kind,
|
|
3336
|
+
applyFailedAt: at,
|
|
3337
|
+
applyFailedByName: user.name,
|
|
3338
|
+
})
|
|
3339
|
+
// The write itself refuses to go backwards: the attempt map above is an
|
|
3340
|
+
// early exit within this process, but an older UPDATE still in flight (or
|
|
3341
|
+
// one from another replica) must never replace a newer refusal.
|
|
3342
|
+
.where(
|
|
3343
|
+
and(
|
|
3344
|
+
eq(changeRequests.number, number),
|
|
3345
|
+
eq(changeRequests.state, 'open'),
|
|
3346
|
+
or(isNull(changeRequests.applyFailedAt), lt(changeRequests.applyFailedAt, at)),
|
|
3347
|
+
),
|
|
3348
|
+
)
|
|
3349
|
+
.returning({ number: changeRequests.number });
|
|
3350
|
+
if (updated.length === 0) return false;
|
|
2491
3351
|
this.prs.invalidateDetailCache(number);
|
|
2492
|
-
this.events?.emit({ kind: 'change-request-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
3352
|
+
this.events?.emit({ kind: 'change-request-apply-failed', number });
|
|
3353
|
+
return true;
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
/**
|
|
3357
|
+
* Forget a request's recorded refusal because a change made it obsolete.
|
|
3358
|
+
* Otherwise the request keeps saying "<name> could not apply this: Waiting on
|
|
3359
|
+
* approval…" after the approval arrived, with nothing dating it.
|
|
3360
|
+
*
|
|
3361
|
+
* WHICH refusals a change makes obsolete is `kinds`: an approval answers only
|
|
3362
|
+
* the gate — a conflict or a git error is untouched by it and must keep
|
|
3363
|
+
* saying so — while a moved source head replaces the revision every kind of
|
|
3364
|
+
* refusal described (omit `kinds` to clear any). A row with no recorded kind
|
|
3365
|
+
* is only cleared by the latter. `recordedBefore` is taken before the change
|
|
3366
|
+
* starts: a refusal recorded after it is newer than the change and stays. Announced on the same event a new refusal
|
|
3367
|
+
* uses — every list and open dialog re-reads the request. Best-effort: the
|
|
3368
|
+
* mutation that triggered it already happened and must not fail on this.
|
|
3369
|
+
*/
|
|
3370
|
+
/**
|
|
3371
|
+
* Whether the merge gate would now let an apply through without bypass — no
|
|
3372
|
+
* hard reason, no warning. Part of the best-effort clear: an evaluation that
|
|
3373
|
+
* fails answers "no" (the refusal stays) instead of failing the approval.
|
|
3374
|
+
*/
|
|
3375
|
+
private gateWouldPass(number: number, approvals: FileApproval[]): boolean {
|
|
3376
|
+
try {
|
|
3377
|
+
const gate = this.reviewWorkflow.evaluateMergeGate({ prNumber: number, state: 'open', approvals });
|
|
3378
|
+
return gate?.mergeable === true && gate.warnings.length === 0;
|
|
3379
|
+
} catch (err) {
|
|
3380
|
+
crLog.warn(`could not re-evaluate the merge gate of change request #${number}; keeping its refusal:`, { err });
|
|
3381
|
+
return false;
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3385
|
+
private async clearApplyFailure(
|
|
3386
|
+
number: number,
|
|
3387
|
+
scope: { kinds?: readonly ChangeRequestApplyFailureKind[]; recordedBefore: Date },
|
|
3388
|
+
): Promise<void> {
|
|
3389
|
+
const { kinds, recordedBefore } = scope;
|
|
3390
|
+
try {
|
|
3391
|
+
const cleared = await this.db
|
|
3392
|
+
.update(changeRequests)
|
|
3393
|
+
.set({
|
|
3394
|
+
applyFailureReason: null,
|
|
3395
|
+
applyFailureConflicts: null,
|
|
3396
|
+
applyFailedAt: null,
|
|
3397
|
+
applyFailedByName: null,
|
|
3398
|
+
applyFailureKind: null,
|
|
3399
|
+
})
|
|
3400
|
+
.where(
|
|
3401
|
+
and(
|
|
3402
|
+
eq(changeRequests.number, number),
|
|
3403
|
+
isNotNull(changeRequests.applyFailedAt),
|
|
3404
|
+
// Only a refusal that PREDATES the change: one an apply recorded
|
|
3405
|
+
// while the change was landing may already have seen it, and is
|
|
3406
|
+
// the newer verdict — never erase it.
|
|
3407
|
+
lt(changeRequests.applyFailedAt, recordedBefore),
|
|
3408
|
+
kinds ? inArray(changeRequests.applyFailureKind, [...kinds]) : undefined,
|
|
3409
|
+
),
|
|
3410
|
+
)
|
|
3411
|
+
.returning({ number: changeRequests.number });
|
|
3412
|
+
if (cleared.length === 0) return;
|
|
3413
|
+
this.prs.invalidateDetailCache(number);
|
|
3414
|
+
this.events?.emit({ kind: 'change-request-apply-failed', number });
|
|
3415
|
+
} catch (err) {
|
|
3416
|
+
crLog.warn(`could not clear the recorded apply failure of change request #${number}:`, { err });
|
|
3417
|
+
}
|
|
2497
3418
|
}
|
|
2498
3419
|
|
|
2499
3420
|
/**
|
|
@@ -2544,13 +3465,12 @@ export class WorkflowService implements IWorkflowService {
|
|
|
2544
3465
|
// "does origin still have it?" probe skips the remote delete.
|
|
2545
3466
|
await this.workspaceService.ensureRemotesFetched(targetWs.id).catch(() => {});
|
|
2546
3467
|
await this.deleteBranchUnlocked(targetWs.id, sourceBranch, user, { systemCleanup: true });
|
|
2547
|
-
|
|
3468
|
+
mergeLog.info(`retired merged source branch "${sourceBranch}"`);
|
|
2548
3469
|
});
|
|
2549
3470
|
} catch (err) {
|
|
2550
|
-
|
|
2551
|
-
`[merge] could not retire the merged source branch of change request #${number} (non-fatal):`,
|
|
3471
|
+
mergeLog.warn(`could not retire the merged source branch of change request #${number} (non-fatal):`, {
|
|
2552
3472
|
err,
|
|
2553
|
-
);
|
|
3473
|
+
});
|
|
2554
3474
|
}
|
|
2555
3475
|
}
|
|
2556
3476
|
|
|
@@ -2674,7 +3594,7 @@ export class WorkflowService implements IWorkflowService {
|
|
|
2674
3594
|
const detail = err instanceof Error ? err.message : String(err);
|
|
2675
3595
|
// Log the raw git/push detail server-side; the thrown error keeps it OFF
|
|
2676
3596
|
// the client-facing 502 message (see RolesYamlPreservationError).
|
|
2677
|
-
|
|
3597
|
+
mergeLog.warn(`roles.yaml preservation failed for #${number}:`, { detail });
|
|
2678
3598
|
throw new RolesYamlPreservationError(detail);
|
|
2679
3599
|
}
|
|
2680
3600
|
}
|
|
@@ -2699,10 +3619,9 @@ export class WorkflowService implements IWorkflowService {
|
|
|
2699
3619
|
);
|
|
2700
3620
|
return stdout.split('\n').map((s) => s.trim()).filter(Boolean);
|
|
2701
3621
|
} catch (err) {
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
);
|
|
3622
|
+
log.warn('listChangedPathsBetweenBranches failed:', {
|
|
3623
|
+
detail: redactTokens(err instanceof Error ? err.message : String(err)),
|
|
3624
|
+
});
|
|
2706
3625
|
return [];
|
|
2707
3626
|
}
|
|
2708
3627
|
}
|
|
@@ -2734,10 +3653,7 @@ export class WorkflowService implements IWorkflowService {
|
|
|
2734
3653
|
paths,
|
|
2735
3654
|
);
|
|
2736
3655
|
} catch (err) {
|
|
2737
|
-
|
|
2738
|
-
'[workflow] eligibleWritersForPathsAtRef failed:',
|
|
2739
|
-
err instanceof Error ? err.message : String(err),
|
|
2740
|
-
);
|
|
3656
|
+
log.warn('eligibleWritersForPathsAtRef failed:', { err });
|
|
2741
3657
|
}
|
|
2742
3658
|
if (!resolved || resolved.size === 0) return '';
|
|
2743
3659
|
|