@bevel-software/platform-core-backend 0.15.1 → 0.19.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 +199 -75
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +49 -2
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +212 -86
- 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/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 +346 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +119 -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 +79 -81
- package/dist/modules/diff/diff.service.js.map +1 -1
- package/dist/modules/kb-fs/bevel-ignore.d.ts +35 -0
- package/dist/modules/kb-fs/bevel-ignore.d.ts.map +1 -0
- package/dist/modules/kb-fs/bevel-ignore.js +71 -0
- 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 +37 -0
- package/dist/modules/kb-fs/git-guarded-filesystem.d.ts.map +1 -0
- package/dist/modules/kb-fs/git-guarded-filesystem.js +104 -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 +9 -5
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +24 -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 +83 -179
- package/dist/modules/mcp/mcp.routes.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +218 -61
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +713 -159
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +4 -2
- 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 +153 -7
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +348 -51
- 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 +51 -3
- 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 +16 -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 +61 -18
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.d.ts +5 -2
- package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.js +5 -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 +7 -1
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +8 -5
- 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 +290 -12
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +924 -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 +1590 -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 +17 -0
- package/dist/shared/git-internals.d.ts.map +1 -0
- package/dist/shared/git-internals.js +123 -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/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/_journal.json +42 -0
- package/package.json +6 -5
- 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 +258 -79
- package/src/core/create-core-services.ts +302 -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/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 +123 -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 +75 -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 +123 -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__/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 +8 -5
- 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.stateless.test.ts +182 -0
- package/src/modules/mcp/__tests__/mcp.service.test.ts +274 -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 +25 -16
- package/src/modules/mcp/mcp.routes.ts +376 -482
- package/src/modules/mcp/mcp.service.ts +846 -176
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +5 -2
- 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 +188 -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 +403 -57
- 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 +124 -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 +56 -3
- package/src/modules/tool-auth/external-api-key.service.ts +73 -25
- package/src/modules/tool-auth/internal-token.service.ts +5 -2
- package/src/modules/tool-auth/key-or-session.middleware.ts +45 -0
- package/src/modules/tool-auth/tool-auth.middleware.ts +16 -7
- 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 +792 -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 +991 -303
- package/src/modules/workspace/workspace.tools.ts +1787 -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 +115 -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 +124 -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/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
|
@@ -1,24 +1,66 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
+
import { logger } from '../../shared/logging.js';
|
|
3
|
+
|
|
4
|
+
const log = logger('workspace.routes');
|
|
2
5
|
import path from 'node:path';
|
|
3
|
-
import { IGNORE_FILENAME } from '
|
|
6
|
+
import { IGNORE_FILENAME, isAbsence, type ITreeWalker } from '../../shared/fs.contract.js';
|
|
7
|
+
import { printable } from '../../shared/printable.js';
|
|
4
8
|
import type { IAdminAccessService } from '../admin/admin.interface.js';
|
|
5
9
|
import express from 'express';
|
|
6
10
|
import type { AuthUser, IWorkflowService } from '@bevel-software/platform-shared';
|
|
7
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
DEFAULT_BRANCH,
|
|
13
|
+
KNOWLEDGE_DIR,
|
|
14
|
+
canonicalRelativePath,
|
|
15
|
+
folderPlaceholderPath,
|
|
16
|
+
isPlatformFile,
|
|
17
|
+
isPlatformRestoreShape,
|
|
18
|
+
platformFileCreationRefusal,
|
|
19
|
+
platformFileRefusal,
|
|
20
|
+
reservedRootDirNames,
|
|
21
|
+
} from '@bevel-software/platform-shared';
|
|
8
22
|
import { FolderTooLargeError, type ReadTreeFilter } from './workspace.service.js';
|
|
9
23
|
import { branchForWorkspaceId } from '../../shared/workspace-id.js';
|
|
10
|
-
import type
|
|
24
|
+
import { EntryExistsError, type WorkspaceService } from './workspace.service.js';
|
|
11
25
|
import type { AuthService } from '../auth/auth.service.js';
|
|
12
26
|
import type { IAccessControl } from '../access/access-control.interface.js';
|
|
13
27
|
import { canReadWorkspacePath, resolveReadableMap, toKbRelative } from '../access-model/kb-read-filter.js';
|
|
14
28
|
import type { ICreatorAccess } from '../access-model/creator.js';
|
|
29
|
+
import type { IChangeReadGate } from '../access-model/change-gate.js';
|
|
15
30
|
import { isRolesYamlPath, assertRolesYamlParsable } from '../access-model/roles-yaml-guard.js';
|
|
16
31
|
import type { WorkflowEventBus } from '../workflow/event-bus.js';
|
|
17
|
-
import { WorkflowDomainError } from '../../shared/domain-errors.js';
|
|
32
|
+
import { PathTraversalError, WorkflowDomainError } from '../../shared/domain-errors.js';
|
|
33
|
+
import { domainErrorBody } from '../../shared/http-errors.js';
|
|
34
|
+
import { assertWithinDirectory } from '../../shared/path-containment.js';
|
|
35
|
+
import { normalizeWorkspacePath } from '../kb-fs/repo-path.js';
|
|
36
|
+
import { hasGitInternalsSegment } from '../../shared/git-internals.js';
|
|
37
|
+
import { createGitInternalsRouteGuard } from './git-internals.middleware.js';
|
|
38
|
+
import { removeEmptyDirs } from './empty-dirs.js';
|
|
18
39
|
import '../auth/auth.middleware.js'; // Express Request augmentation
|
|
40
|
+
import type { SkillSaveCheck } from './workspace.tools.js';
|
|
19
41
|
|
|
20
42
|
const MAX_UPLOAD_BYTES = 50 * 1024 * 1024; // 50 MB
|
|
21
43
|
|
|
44
|
+
/**
|
|
45
|
+
* One file identity from one request field, or `null` when the caller sent
|
|
46
|
+
* nothing usable.
|
|
47
|
+
*
|
|
48
|
+
* TYPED, not cast: a repeated `?path=a&path=b` arrives as an array and a JSON
|
|
49
|
+
* body can hold anything, so `as string` would hand a non-string to the
|
|
50
|
+
* canonicaliser and throw a 500 on what is a client mistake.
|
|
51
|
+
*
|
|
52
|
+
* CANONICAL, because the mutating verbs on this surface coordinate on the
|
|
53
|
+
* path: `PUT` takes an in-process write turn on it, all of them take the
|
|
54
|
+
* workflow lock row keyed by it, and the bytes live at it. A save spelled
|
|
55
|
+
* `x/a.md` and a delete spelled `./x//a.md` would otherwise take two
|
|
56
|
+
* different lock rows for one file and interleave. See
|
|
57
|
+
* `canonicalRelativePath` for what it refuses to touch.
|
|
58
|
+
*/
|
|
59
|
+
function requestPath(value: unknown): string | null {
|
|
60
|
+
if (typeof value !== 'string' || value.length === 0) return null;
|
|
61
|
+
return canonicalRelativePath(value);
|
|
62
|
+
}
|
|
63
|
+
|
|
22
64
|
/**
|
|
23
65
|
* Workspaces are per-branch (PLAN §3). Any authenticated user can access
|
|
24
66
|
* any branch's workspace; coordination of concurrent edits lives in the
|
|
@@ -43,8 +85,64 @@ export function createWorkspaceRoutes(
|
|
|
43
85
|
kbDirName: string,
|
|
44
86
|
creatorAccess: ICreatorAccess,
|
|
45
87
|
adminAccess: IAdminAccessService,
|
|
88
|
+
disk: ITreeWalker,
|
|
89
|
+
/** Save-time skill check: `PUT /file` on a SKILL.md answers with `warnings` (advisory, never a refusal). */
|
|
90
|
+
skillSaveCheck?: SkillSaveCheck,
|
|
91
|
+
/**
|
|
92
|
+
* Read-before-write, asked ahead of the lock where a route puts bytes on
|
|
93
|
+
* disk BEFORE it locks them (archive extraction). Every other route's writes
|
|
94
|
+
* meet the same gate inside `acquireLock`. Optional so route harnesses that
|
|
95
|
+
* exercise other behaviour need not wire it.
|
|
96
|
+
*/
|
|
97
|
+
changeGate?: IChangeReadGate,
|
|
46
98
|
): express.Router {
|
|
47
99
|
const router = express.Router();
|
|
100
|
+
const gitInternalsRouteGuard = createGitInternalsRouteGuard(workspaceService);
|
|
101
|
+
|
|
102
|
+
// The git folder is refused ahead of every route here by
|
|
103
|
+
// `createGitInternalsRouteGuard`, which the app mounts once for the whole
|
|
104
|
+
// `/workspace/:id` prefix (see `git-internals.middleware.ts`). Mounted here
|
|
105
|
+
// too so this router carries the rule wherever it is mounted on its own.
|
|
106
|
+
router.use("/workspace/:id", gitInternalsRouteGuard);
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The REPOSITORY path a request names, or `null` once the refusal has been
|
|
110
|
+
* sent.
|
|
111
|
+
*
|
|
112
|
+
* Every handler on this surface calls this FIRST, before the access check and
|
|
113
|
+
* before the service, so one path is what is checked and what is written.
|
|
114
|
+
* That was the hole: the access helpers stripped `<kbDirName>/` when it was
|
|
115
|
+
* there and otherwise took the path as written, so an unprefixed
|
|
116
|
+
* `KnowledgeBase/Reports` was judged as a repository path and then created
|
|
117
|
+
* beside the repository — a folder, eight documents and a `Plugins/` tree on
|
|
118
|
+
* core-staging, none of it ever committed. The normaliser closes it by making
|
|
119
|
+
* the unprefixed spelling MEAN the repository path it was judged as.
|
|
120
|
+
*/
|
|
121
|
+
function inRepo(res: express.Response, wsPath: string): string | null {
|
|
122
|
+
try {
|
|
123
|
+
return normalizeWorkspacePath(wsPath, kbDirName);
|
|
124
|
+
} catch (err) {
|
|
125
|
+
sendError(res, err);
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* A repository path's absolute location, checked against the repository root.
|
|
132
|
+
*
|
|
133
|
+
* The ONE place this router resolves a workspace path — the delete fast-path,
|
|
134
|
+
* the platform-restore look and the folder-keep all used to call
|
|
135
|
+
* `path.resolve(workspaceDir, …)` for themselves, against the WORKSPACE dir,
|
|
136
|
+
* which is the containment an unprefixed path satisfied on its way to being
|
|
137
|
+
* written beside the checkout. A drift-guard test fails if a second one
|
|
138
|
+
* appears.
|
|
139
|
+
*/
|
|
140
|
+
async function absoluteInRepo(workspaceId: string, repoRelative: string): Promise<string> {
|
|
141
|
+
const workspaceDir = await workspaceService.getWorkspacePath(workspaceId);
|
|
142
|
+
const absolute = path.resolve(workspaceDir, repoRelative);
|
|
143
|
+
assertWithinDirectory(absolute, path.join(workspaceDir, kbDirName));
|
|
144
|
+
return absolute;
|
|
145
|
+
}
|
|
48
146
|
|
|
49
147
|
function authenticated(
|
|
50
148
|
req: express.Request,
|
|
@@ -84,12 +182,49 @@ export function createWorkspaceRoutes(
|
|
|
84
182
|
}
|
|
85
183
|
return user;
|
|
86
184
|
} catch (err) {
|
|
87
|
-
|
|
185
|
+
log.error('requireUser failed:', { err });
|
|
88
186
|
res.status(500).json({ error: 'Internal server error' });
|
|
89
187
|
return null;
|
|
90
188
|
}
|
|
91
189
|
}
|
|
92
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Whether a failed op is one that provably touched no bytes, so its lock
|
|
193
|
+
* may be released with the disk and the commit queue left exactly as they
|
|
194
|
+
* are (`releaseLockUntouched`) rather than reset to HEAD.
|
|
195
|
+
*
|
|
196
|
+
* This is not tidiness. A release that discards resets the PATH, not "this
|
|
197
|
+
* request's changes" — git has no notion of the latter — so when the path
|
|
198
|
+
* holds a landed save whose commit is still queued (commits run out of band
|
|
199
|
+
* in the pending-commits worker), the discard destroys that save. Two moves
|
|
200
|
+
* racing onto the same free name are exactly that situation: the winner
|
|
201
|
+
* lands and enqueues, the loser then takes the same destination lock, is
|
|
202
|
+
* refused because the name is now taken, and its unwind would throw the
|
|
203
|
+
* winner's file away. One refusal, two lost files.
|
|
204
|
+
*
|
|
205
|
+
* A destination-taken refusal qualifies because nothing it can do writes:
|
|
206
|
+
* the preflight throws before the move is attempted, and the move's own
|
|
207
|
+
* no-clobber calls (`link`, `mkdir`, `open` with `O_EXCL` — see
|
|
208
|
+
* `shared/rename-no-replace.ts`) fail without creating anything, with the
|
|
209
|
+
* folder claim rolling itself back. `LockingFilesystem.withLock` draws the
|
|
210
|
+
* same line for its `CheckRefusal`, in the same words: a refusal that wrote
|
|
211
|
+
* nothing releases untouched.
|
|
212
|
+
*
|
|
213
|
+
* Deliberately a closed list of refusal TYPES rather than a guess at what
|
|
214
|
+
* an op did. An unrecognised failure keeps the discarding release, which is
|
|
215
|
+
* the fail-closed side: at worst it throws away bytes nobody promised to
|
|
216
|
+
* keep, where the other mistake throws away bytes someone was told were
|
|
217
|
+
* saved.
|
|
218
|
+
*
|
|
219
|
+
* One type, not two: the move's lower-level `DestinationTakenError` never
|
|
220
|
+
* reaches this layer — `moveEntry` converts it into `EntryExistsError`, the
|
|
221
|
+
* refusal this surface answers 409 with — so recognising it here as well
|
|
222
|
+
* would be a branch nothing can take.
|
|
223
|
+
*/
|
|
224
|
+
function wroteNothing(err: unknown): boolean {
|
|
225
|
+
return err instanceof EntryExistsError;
|
|
226
|
+
}
|
|
227
|
+
|
|
93
228
|
/**
|
|
94
229
|
* Acquire the workflow lock for `(workspaceId, branch, targetPath)`, run
|
|
95
230
|
* `op`, then release. Release commits + pushes the file as a one-file
|
|
@@ -117,7 +252,17 @@ export function createWorkspaceRoutes(
|
|
|
117
252
|
* `skipPush` option is gone — under the queue model commits +
|
|
118
253
|
* pushes happen out of band in the worker, not inline here.)
|
|
119
254
|
*/
|
|
120
|
-
options?: {
|
|
255
|
+
options?: {
|
|
256
|
+
skipFsTreeEvent?: boolean;
|
|
257
|
+
/**
|
|
258
|
+
* This acquire is the destination side of an admin putting a misplaced
|
|
259
|
+
* platform file back, coming from `source`. Passed straight to
|
|
260
|
+
* `acquireLock`, which VERIFIES both halves — that the move is a restore
|
|
261
|
+
* at all, and that this caller may make it — rather than believing
|
|
262
|
+
* either. See the `IWorkflowService.acquireLock` contract.
|
|
263
|
+
*/
|
|
264
|
+
platformRestore?: { source: string };
|
|
265
|
+
},
|
|
121
266
|
): Promise<T> {
|
|
122
267
|
const branch = branchForWorkspaceId(workspaceId);
|
|
123
268
|
// If the caller already holds the lock, do NOT acquire-and-release
|
|
@@ -153,7 +298,9 @@ export function createWorkspaceRoutes(
|
|
|
153
298
|
eventBus.emit({ kind: 'fs-tree-changed', workspaceId, branch });
|
|
154
299
|
return result;
|
|
155
300
|
}
|
|
156
|
-
const acquired = await workflowService.acquireLock(workspaceId, branch, targetPath, user
|
|
301
|
+
const acquired = await workflowService.acquireLock(workspaceId, branch, targetPath, user, {
|
|
302
|
+
platformRestore: options?.platformRestore,
|
|
303
|
+
});
|
|
157
304
|
if (!acquired.acquired) {
|
|
158
305
|
const holder = acquired.lock.holderName || 'another user';
|
|
159
306
|
const err: Error & { status?: number } = new Error(
|
|
@@ -162,16 +309,22 @@ export function createWorkspaceRoutes(
|
|
|
162
309
|
err.status = 409;
|
|
163
310
|
throw err;
|
|
164
311
|
}
|
|
165
|
-
//
|
|
312
|
+
// Three release modes, the same three `LockingFilesystem.withLock` uses,
|
|
313
|
+
// depending on what the op did:
|
|
314
|
+
//
|
|
315
|
+
// - op() FAILED having possibly WRITTEN → drop the lock WITHOUT
|
|
316
|
+
// enqueueing a commit. The op may have written partial bytes to disk
|
|
317
|
+
// before throwing (write that errored mid-stream, etc.). A normal
|
|
318
|
+
// `releaseLock` would enqueue a commit for whatever's on disk and the
|
|
319
|
+
// worker would silently persist that partial state as a real
|
|
320
|
+
// committed change. `releaseLockNoCommit` resets the path to HEAD, so
|
|
321
|
+
// partial bytes never become a committed change with the user's name
|
|
322
|
+
// on it.
|
|
166
323
|
//
|
|
167
|
-
// - op() FAILED
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
// would silently persist that partial state as a real committed
|
|
172
|
-
// change. `releaseLockNoCommit` drops the lock row only —
|
|
173
|
-
// partial disk state stays where it is but never becomes a
|
|
174
|
-
// committed change with the user's name on it.
|
|
324
|
+
// - op() FAILED having written NOTHING → `releaseLockUntouched`: drop
|
|
325
|
+
// the lock row and leave both the disk and the commit queue exactly
|
|
326
|
+
// as they are. See `wroteNothing` for which refusals qualify and why
|
|
327
|
+
// the distinction is load-bearing rather than tidy.
|
|
175
328
|
//
|
|
176
329
|
// - op() SUCCEEDED → release. The new releaseLock enqueues a
|
|
177
330
|
// pending-commit row (the actual `commitFile + push` runs out
|
|
@@ -185,12 +338,17 @@ export function createWorkspaceRoutes(
|
|
|
185
338
|
result = await op();
|
|
186
339
|
opSucceeded = true;
|
|
187
340
|
} catch (err) {
|
|
341
|
+
const untouched = wroteNothing(err);
|
|
188
342
|
try {
|
|
189
|
-
|
|
343
|
+
if (untouched) {
|
|
344
|
+
await workflowService.releaseLockUntouched(workspaceId, branch, targetPath, user);
|
|
345
|
+
} else {
|
|
346
|
+
await workflowService.releaseLockNoCommit(workspaceId, branch, targetPath, user);
|
|
347
|
+
}
|
|
190
348
|
} catch (releaseErr) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
349
|
+
log.warn(
|
|
350
|
+
`${untouched ? 'releaseLockUntouched' : 'releaseLockNoCommit'} failed after op error for "${targetPath}":`,
|
|
351
|
+
{ err: releaseErr },
|
|
194
352
|
);
|
|
195
353
|
}
|
|
196
354
|
throw err;
|
|
@@ -217,19 +375,65 @@ export function createWorkspaceRoutes(
|
|
|
217
375
|
return result;
|
|
218
376
|
}
|
|
219
377
|
|
|
378
|
+
/**
|
|
379
|
+
* Keep the folder a removal just emptied. A folder exists until it is
|
|
380
|
+
* deleted explicitly, so when deleting a file (or a subfolder), or moving
|
|
381
|
+
* one out, leaves its parent empty, the parent gets the placeholder in its
|
|
382
|
+
* own lock cycle — committed like any other save, within the same request.
|
|
383
|
+
* Only folders inside the repository qualify, never the clone folder
|
|
384
|
+
* itself.
|
|
385
|
+
*
|
|
386
|
+
* It runs in the folder's turn, which an explicit folder delete also takes,
|
|
387
|
+
* and looks again inside it: a folder that is gone by then was deleted
|
|
388
|
+
* explicitly and stays gone, and one that gained an entry needs nothing.
|
|
389
|
+
* The placeholder is only ever written into a folder that exists, and never
|
|
390
|
+
* through a link (see `writeFolderPlaceholder`).
|
|
391
|
+
*
|
|
392
|
+
* A failure is the request's failure: the removal landed, but a request
|
|
393
|
+
* that answers success would leave a folder that vanishes on the next
|
|
394
|
+
* clone, which is exactly what this rule forbids. It keeps its own status
|
|
395
|
+
* (a contended placeholder lock is still a 409) and only gains the context.
|
|
396
|
+
*/
|
|
397
|
+
async function keepFolderOf(
|
|
398
|
+
workspaceId: string,
|
|
399
|
+
user: AuthUser,
|
|
400
|
+
removedPath: string,
|
|
401
|
+
): Promise<void> {
|
|
402
|
+
const trimmed = removedPath.replace(/\/+$/, '');
|
|
403
|
+
const dir = trimmed.includes('/') ? trimmed.slice(0, trimmed.lastIndexOf('/')) : '';
|
|
404
|
+
if (!dir.startsWith(`${kbDirName}/`)) return;
|
|
405
|
+
try {
|
|
406
|
+
const absolute = await absoluteInRepo(workspaceId, dir);
|
|
407
|
+
await workspaceService.withFolderTurn(workspaceId, dir, async () => {
|
|
408
|
+
if (!(await isEmptyFolder(absolute))) return;
|
|
409
|
+
// One tree refresh per request: the removal already announced it.
|
|
410
|
+
await withLock(
|
|
411
|
+
workspaceId,
|
|
412
|
+
user,
|
|
413
|
+
folderPlaceholderPath(dir),
|
|
414
|
+
() => workspaceService.writeFolderPlaceholder(workspaceId, dir),
|
|
415
|
+
{ skipFsTreeEvent: true },
|
|
416
|
+
);
|
|
417
|
+
});
|
|
418
|
+
} catch (err) {
|
|
419
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
420
|
+
log.error(`could not keep the folder ${printable(dir)} after removing ${printable(removedPath)}: ${printable(reason)}`);
|
|
421
|
+
throw withKeptFolderContext(err, `"${removedPath}" was removed, but its folder "${dir}" could not be kept: ${reason}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
220
425
|
/**
|
|
221
426
|
* Map any thrown error to an HTTP response. Centralises the response
|
|
222
427
|
* shape so each route handler stays focused on its own logic.
|
|
223
|
-
* - `WorkflowDomainError` → its `.status` + `.payload`
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
* - Could not read zip → 422
|
|
428
|
+
* - `WorkflowDomainError` → its `.status` + `.payload` (a traversal is a
|
|
429
|
+
* 403, an invalid path a 400, an unreadable archive a 422 — each by
|
|
430
|
+
* its TYPE; this surface used to recognise all three by their message)
|
|
227
431
|
* - Anything else with a `.status` → that status
|
|
228
432
|
* - Default → 500
|
|
229
433
|
*/
|
|
230
434
|
function sendError(res: express.Response, err: unknown): void {
|
|
231
435
|
if (err instanceof WorkflowDomainError) {
|
|
232
|
-
res.status(err.status).json(
|
|
436
|
+
res.status(err.status).json(domainErrorBody(err));
|
|
233
437
|
return;
|
|
234
438
|
}
|
|
235
439
|
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
@@ -238,18 +442,6 @@ export function createWorkspaceRoutes(
|
|
|
238
442
|
res.status(status).json({ error: msg });
|
|
239
443
|
return;
|
|
240
444
|
}
|
|
241
|
-
if (msg === 'Path traversal detected') {
|
|
242
|
-
res.status(403).json({ error: msg });
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
if (msg.startsWith('Invalid path') || msg.startsWith('Only .zip')) {
|
|
246
|
-
res.status(400).json({ error: msg });
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
if (msg.startsWith('Could not read zip file')) {
|
|
250
|
-
res.status(422).json({ error: msg });
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
445
|
res.status(500).json({ error: msg });
|
|
254
446
|
}
|
|
255
447
|
|
|
@@ -292,10 +484,7 @@ export function createWorkspaceRoutes(
|
|
|
292
484
|
// Log the full stack so the next 500 isn't a guessing game — the
|
|
293
485
|
// bare `error.message` we returned before lost most diagnostic
|
|
294
486
|
// signal (cause chain, stack frames, error class).
|
|
295
|
-
|
|
296
|
-
`[workspace.routes] GET /workspace failed branch=${req.query.branch ?? '(default)'}:`,
|
|
297
|
-
error instanceof Error ? error.stack ?? error.message : error,
|
|
298
|
-
);
|
|
487
|
+
log.error(`GET /workspace failed branch=${req.query.branch ?? '(default)'}:`, { err: error });
|
|
299
488
|
const msg = error instanceof Error ? error.message : 'Unknown error';
|
|
300
489
|
res.status(500).json({ error: msg });
|
|
301
490
|
}
|
|
@@ -388,12 +577,11 @@ export function createWorkspaceRoutes(
|
|
|
388
577
|
let allowed: boolean;
|
|
389
578
|
try {
|
|
390
579
|
// `download` is a per-path verb in access.md, resolved via the same
|
|
391
|
-
// chain walk as `write`.
|
|
392
|
-
//
|
|
393
|
-
//
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
: relativePath;
|
|
580
|
+
// chain walk as `write`. The caller has already normalised the path into
|
|
581
|
+
// the repository, so this only takes the prefix off — where it used to
|
|
582
|
+
// pass an unprefixed path through untouched and judge a stray as if it
|
|
583
|
+
// were inside.
|
|
584
|
+
const repoRelative = toKbRelative(relativePath, kbDirName) ?? relativePath;
|
|
397
585
|
allowed = await accessControl.canDownload(workspaceId, user.email, repoRelative);
|
|
398
586
|
} catch (err) {
|
|
399
587
|
sendError(res, err);
|
|
@@ -459,7 +647,7 @@ export function createWorkspaceRoutes(
|
|
|
459
647
|
*/
|
|
460
648
|
function buildTreeReadFilter(workspaceId: string, userEmail: string): ReadTreeFilter {
|
|
461
649
|
return async (wsRelPaths) => {
|
|
462
|
-
const verdict = await resolveReadableMap(
|
|
650
|
+
const verdict: Map<string, boolean | 'unlisted'> = await resolveReadableMap(
|
|
463
651
|
(w, e, rels) => accessControl.canReadBatch(w, e, rels),
|
|
464
652
|
workspaceId,
|
|
465
653
|
userEmail,
|
|
@@ -484,7 +672,7 @@ export function createWorkspaceRoutes(
|
|
|
484
672
|
// `.bevelignore` is ADMIN-ONLY. It is the file that decides what the file
|
|
485
673
|
// tree and the agent view show at all, so it is deployment configuration
|
|
486
674
|
// rather than knowledge — and it sits alone in being visible: its
|
|
487
|
-
// siblings (`.gitignore`, `roles.yaml`, `access.md`,
|
|
675
|
+
// siblings (`.gitignore`, `roles.yaml`, `access.md`, the agent guide) are
|
|
488
676
|
// already hidden from every reader by the shipped ignore rules. Somebody
|
|
489
677
|
// who cannot act on it gains a puzzle; somebody who can needs to reach it.
|
|
490
678
|
//
|
|
@@ -497,9 +685,13 @@ export function createWorkspaceRoutes(
|
|
|
497
685
|
// on the default branch precisely so that editing `roles.yaml` on your
|
|
498
686
|
// own branch cannot promote you, and the same reasoning applies to
|
|
499
687
|
// anything gated on being an admin.
|
|
688
|
+
//
|
|
689
|
+
// `'unlisted'`, not `false`: hiding it withholds no content, so a
|
|
690
|
+
// non-admin in an empty knowledge base (every one ships this file) is
|
|
691
|
+
// told it is empty, not that something is being kept from them.
|
|
500
692
|
const ignoreFiles = wsRelPaths.filter((wp) => path.basename(wp) === IGNORE_FILENAME);
|
|
501
693
|
if (ignoreFiles.length > 0 && !(await adminAccess.isAdmin(userEmail))) {
|
|
502
|
-
for (const wp of ignoreFiles) verdict.set(wp,
|
|
694
|
+
for (const wp of ignoreFiles) verdict.set(wp, 'unlisted');
|
|
503
695
|
}
|
|
504
696
|
return verdict;
|
|
505
697
|
};
|
|
@@ -514,44 +706,70 @@ export function createWorkspaceRoutes(
|
|
|
514
706
|
* grant on the same new directory survives. Runs BEFORE the creation itself
|
|
515
707
|
* so the explorer never shows-then-hides the new subtree; if the creation
|
|
516
708
|
* subsequently fails, the leftover is an empty new folder readable only by
|
|
517
|
-
* its creator.
|
|
518
|
-
*
|
|
709
|
+
* its creator.
|
|
710
|
+
*
|
|
711
|
+
* A failure here FAILS the creation, and propagates as it is. The seed is
|
|
712
|
+
* planned only for a new folder at a root the creator cannot read — the one
|
|
713
|
+
* creation the read gate lets past an unreadable spot — so without the
|
|
714
|
+
* grant the folder would come into existence invisible to the person who
|
|
715
|
+
* made it, which is exactly what the gate exists to prevent. The seed's
|
|
716
|
+
* own lock passes that gate too: if the folder appeared under someone else
|
|
717
|
+
* between the plan and this write, the refusal is theirs to see, and
|
|
718
|
+
* nothing lands.
|
|
519
719
|
*/
|
|
520
720
|
async function seedCreatorAccessMd(
|
|
521
721
|
workspaceId: string,
|
|
522
722
|
user: AuthUser,
|
|
523
723
|
plan: { wsRelPath: string; apply: (current: string) => string },
|
|
524
724
|
): Promise<void> {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
creatorAccess.noteAccessFileWritten(workspaceId);
|
|
539
|
-
} catch (err) {
|
|
540
|
-
console.warn(
|
|
541
|
-
`[workspace.routes] creator access.md seed failed for "${plan.wsRelPath}":`,
|
|
542
|
-
err instanceof Error ? err.message : err,
|
|
543
|
-
);
|
|
544
|
-
}
|
|
725
|
+
await withLock(workspaceId, user, plan.wsRelPath, async () => {
|
|
726
|
+
let current = '';
|
|
727
|
+
try {
|
|
728
|
+
current = await workspaceService.readFile(workspaceId, plan.wsRelPath);
|
|
729
|
+
} catch {
|
|
730
|
+
// Not there yet — the normal case for a brand-new directory.
|
|
731
|
+
}
|
|
732
|
+
const next = plan.apply(current);
|
|
733
|
+
if (next !== current) {
|
|
734
|
+
await workspaceService.writeFile(workspaceId, plan.wsRelPath, next);
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
creatorAccess.noteAccessFileWritten(workspaceId);
|
|
545
738
|
}
|
|
546
739
|
|
|
740
|
+
/**
|
|
741
|
+
* GET /workspace/:id/file/raw?path=<file>[&download=1][&v=<n>]
|
|
742
|
+
*
|
|
743
|
+
* The bytes of one workspace file: for the document renderers' fetches, the
|
|
744
|
+
* file tree's Download, and the `<img>` tags the markdown pipeline emits for
|
|
745
|
+
* ``. An `<img>` sends no Authorization header, so it
|
|
746
|
+
* is the auth middleware's `bevel_token` cookie fallback that lets a plain
|
|
747
|
+
* image tag through.
|
|
748
|
+
*
|
|
749
|
+
* request ──▶ auth: Bearer, else the cookie
|
|
750
|
+
* ──▶ read gate on the path 403 if the caller may not read it
|
|
751
|
+
* ──▶ download gate, if ?download=1 403 without the download: verb
|
|
752
|
+
* ──▶ readFileBinary 404 missing, 403 traversal
|
|
753
|
+
* ──▶ Content-Type from the extension, nosniff, a CSP sandbox for
|
|
754
|
+
* inline svg, attachment disposition for a download, and
|
|
755
|
+
* Cache-Control: private, no-cache
|
|
756
|
+
* ──▶ res.send(buffer): Express sets a weak ETag and answers 304 to
|
|
757
|
+
* a matching If-None-Match, so a revisited page costs a read
|
|
758
|
+
* and a hash per image rather than the bytes on the wire
|
|
759
|
+
*
|
|
760
|
+
* `?v=` is not read here. The frontend bumps it when it learns an image
|
|
761
|
+
* changed, so the browser asks for a URL it has not cached.
|
|
762
|
+
*/
|
|
547
763
|
router.get('/workspace/:id/file/raw', async (req, res) => {
|
|
548
764
|
const id = authenticated(req, res);
|
|
549
765
|
if (id === null) return;
|
|
550
|
-
const
|
|
551
|
-
if (
|
|
766
|
+
const requested = req.query.path;
|
|
767
|
+
if (typeof requested !== 'string' || requested.length === 0) {
|
|
552
768
|
res.status(400).json({ error: 'path query parameter is required' });
|
|
553
769
|
return;
|
|
554
770
|
}
|
|
771
|
+
const filePath = inRepo(res, requested);
|
|
772
|
+
if (filePath === null) return;
|
|
555
773
|
// `?download=1` flips this from inline-serve (used by PdfRenderer and
|
|
556
774
|
// the image renderers) to "save to disk" — and the save path is gated
|
|
557
775
|
// on per-path `download:` rules in access.md. The inline path stays
|
|
@@ -611,11 +829,18 @@ export function createWorkspaceRoutes(
|
|
|
611
829
|
`attachment; filename*=UTF-8''${encodeURIComponent(basename)}`,
|
|
612
830
|
);
|
|
613
831
|
}
|
|
832
|
+
// `private`: this is one person's authenticated file, and a shared cache
|
|
833
|
+
// (a CDN, a corporate proxy) must not hand it to the next person.
|
|
834
|
+
// `no-cache`: the browser may keep it but asks before reusing it, which
|
|
835
|
+
// the ETag answers with a 304; a picture replaced under the same name is
|
|
836
|
+
// therefore never older than one revalidation.
|
|
837
|
+
res.setHeader('Cache-Control', 'private, no-cache');
|
|
614
838
|
res.send(buffer);
|
|
615
839
|
} catch (error) {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
840
|
+
// A traversal is the caller's 403; every other read failure here is the
|
|
841
|
+
// route's honest 404 (the image either is not there or cannot be shown).
|
|
842
|
+
if (error instanceof PathTraversalError) {
|
|
843
|
+
sendError(res, error);
|
|
619
844
|
return;
|
|
620
845
|
}
|
|
621
846
|
res.status(404).json({ error: 'File not found' });
|
|
@@ -650,11 +875,13 @@ export function createWorkspaceRoutes(
|
|
|
650
875
|
// Trim whitespace and strip trailing slashes — `foo/` would otherwise
|
|
651
876
|
// basename to `''` and produce a misleading `.zip` filename. Done
|
|
652
877
|
// before the empty check so `' / '` is also caught.
|
|
653
|
-
const
|
|
654
|
-
if (!
|
|
878
|
+
const trimmedPath = rawPath.trim().replace(/\/+$/, '');
|
|
879
|
+
if (!trimmedPath) {
|
|
655
880
|
res.status(400).json({ error: 'path query parameter is required' });
|
|
656
881
|
return;
|
|
657
882
|
}
|
|
883
|
+
const folderPath = inRepo(res, trimmedPath);
|
|
884
|
+
if (folderPath === null) return;
|
|
658
885
|
if (req.query.download !== '1') {
|
|
659
886
|
res.status(400).json({ error: 'download=1 is required for folder zip downloads' });
|
|
660
887
|
return;
|
|
@@ -680,11 +907,11 @@ export function createWorkspaceRoutes(
|
|
|
680
907
|
res.status(413).json({ error: error.message });
|
|
681
908
|
return;
|
|
682
909
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
res.status(403).json({ error: msg });
|
|
910
|
+
if (error instanceof PathTraversalError) {
|
|
911
|
+
sendError(res, error);
|
|
686
912
|
return;
|
|
687
913
|
}
|
|
914
|
+
const msg = error instanceof Error ? error.message : 'Unknown error';
|
|
688
915
|
if (msg === 'Not a directory') {
|
|
689
916
|
res.status(400).json({ error: msg });
|
|
690
917
|
return;
|
|
@@ -697,11 +924,13 @@ export function createWorkspaceRoutes(
|
|
|
697
924
|
router.get('/workspace/:id/file', async (req, res) => {
|
|
698
925
|
const id = authenticated(req, res);
|
|
699
926
|
if (id === null) return;
|
|
700
|
-
const
|
|
701
|
-
if (
|
|
927
|
+
const requested = requestPath(req.query.path);
|
|
928
|
+
if (requested === null) {
|
|
702
929
|
res.status(400).json({ error: 'path query parameter is required' });
|
|
703
930
|
return;
|
|
704
931
|
}
|
|
932
|
+
const filePath = inRepo(res, requested);
|
|
933
|
+
if (filePath === null) return;
|
|
705
934
|
if (!(await requireReadPermission(req, res, id, filePath))) return;
|
|
706
935
|
try {
|
|
707
936
|
const content = await workspaceService.readFile(id, filePath);
|
|
@@ -711,23 +940,46 @@ export function createWorkspaceRoutes(
|
|
|
711
940
|
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
|
|
712
941
|
res.json({ content });
|
|
713
942
|
} catch (error) {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
943
|
+
// 404 means ONE thing here: there is no file at this path. That is
|
|
944
|
+
// ENOENT, a parent that is not a directory (ENOTDIR), and a directory
|
|
945
|
+
// asked for as a file (EISDIR). Every other read failure keeps its own
|
|
946
|
+
// status, because callers act on the difference: the inline
|
|
947
|
+
// agent-description editor opens EMPTY on a 404 (a knowledge base older
|
|
948
|
+
// than the template has no such file yet), so dressing an unreadable
|
|
949
|
+
// file up as a missing one would offer an empty editor over content the
|
|
950
|
+
// save then overwrites.
|
|
951
|
+
if (isAbsence(error) || (error as NodeJS.ErrnoException).code === 'EISDIR') {
|
|
952
|
+
res.status(404).json({ error: 'File not found' });
|
|
717
953
|
return;
|
|
718
954
|
}
|
|
719
|
-
|
|
955
|
+
// Traversal stays a 403 and a malformed workspace id its domain status:
|
|
956
|
+
// both carry messages written to be read by the caller. (A traversal IS
|
|
957
|
+
// a domain error now, so the one check covers both.)
|
|
958
|
+
if (error instanceof WorkflowDomainError) {
|
|
959
|
+
sendError(res, error);
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
// Anything else is a real failure, and it says so WITHOUT quoting
|
|
963
|
+
// itself: an errno message carries absolute workspace paths and a
|
|
964
|
+
// bootstrap failure carries git's stderr, neither of which belongs in a
|
|
965
|
+
// response body. The distinction this route exists to make is still
|
|
966
|
+
// made — an unreadable file is not a missing one — it is just not
|
|
967
|
+
// narrated to the client.
|
|
968
|
+
log.warn(`GET /file failed for "${filePath}" in "${id}":`, { err: error });
|
|
969
|
+
res.status(500).json({ error: "Couldn't read the file." });
|
|
720
970
|
}
|
|
721
971
|
});
|
|
722
972
|
|
|
723
973
|
router.delete('/workspace/:id/file', async (req, res) => {
|
|
724
974
|
const id = authenticated(req, res);
|
|
725
975
|
if (id === null) return;
|
|
726
|
-
const
|
|
727
|
-
if (
|
|
976
|
+
const requested = requestPath(req.query.path);
|
|
977
|
+
if (requested === null) {
|
|
728
978
|
res.status(400).json({ error: 'path query parameter is required' });
|
|
729
979
|
return;
|
|
730
980
|
}
|
|
981
|
+
const filePath = inRepo(res, requested);
|
|
982
|
+
if (filePath === null) return;
|
|
731
983
|
const user = await requireUser(req, res);
|
|
732
984
|
if (!user) return;
|
|
733
985
|
try {
|
|
@@ -741,19 +993,12 @@ export function createWorkspaceRoutes(
|
|
|
741
993
|
// emits are also coalesced into a single end-of-batch event so the
|
|
742
994
|
// explorer doesn't refresh N times.
|
|
743
995
|
const workspaceDir = await workspaceService.getWorkspacePath(id);
|
|
744
|
-
|
|
745
|
-
//
|
|
746
|
-
//
|
|
747
|
-
//
|
|
748
|
-
//
|
|
749
|
-
|
|
750
|
-
// dir-delete branch operate on directories outside the workspace.
|
|
751
|
-
const workspaceRoot = path.resolve(workspaceDir);
|
|
752
|
-
if (absolute !== workspaceRoot && !absolute.startsWith(workspaceRoot + path.sep)) {
|
|
753
|
-
const err: Error & { status?: number } = new Error('Path traversal detected');
|
|
754
|
-
err.status = 403;
|
|
755
|
-
throw err;
|
|
756
|
-
}
|
|
996
|
+
// Boundary check before any `fs.*` call — against the REPOSITORY root,
|
|
997
|
+
// which is where `absoluteInRepo` holds it. The single-file path bottoms
|
|
998
|
+
// out in `workspaceService.deleteFile` (which resolves inside the
|
|
999
|
+
// repository itself), but this fast-path needs the absolute for
|
|
1000
|
+
// `fs.stat` / `fs.rm` / `enumerateFilesUnder`.
|
|
1001
|
+
const absolute = await absoluteInRepo(id, filePath);
|
|
757
1002
|
let stat: { isDirectory: () => boolean } | null = null;
|
|
758
1003
|
try {
|
|
759
1004
|
stat = await fs.stat(absolute);
|
|
@@ -761,56 +1006,77 @@ export function createWorkspaceRoutes(
|
|
|
761
1006
|
// Not on disk — let workspaceService.deleteFile return its own 404.
|
|
762
1007
|
}
|
|
763
1008
|
if (stat?.isDirectory()) {
|
|
764
|
-
const filesInDir = await enumerateFilesUnder(absolute, workspaceDir);
|
|
765
1009
|
const branch = branchForWorkspaceId(id);
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
relFile,
|
|
771
|
-
async () => {
|
|
772
|
-
await workspaceService.deleteFile(id, relFile);
|
|
773
|
-
},
|
|
774
|
-
{ skipFsTreeEvent: true },
|
|
775
|
-
);
|
|
776
|
-
}
|
|
777
|
-
// No explicit push here — each per-file release enqueues a
|
|
778
|
-
// pending-commits row, and the background worker drains them
|
|
779
|
-
// (commit + push) on its own schedule. The N round-trips that
|
|
780
|
-
// the old skipPush+pushBranch pattern collapsed into one happen
|
|
781
|
-
// serially in the worker; user perception is unchanged because
|
|
782
|
-
// the disk-side delete is what other sessions see via
|
|
783
|
-
// `fs-tree-changed`.
|
|
784
|
-
// Sweep the now-empty directory subtree off disk. Git doesn't track
|
|
785
|
-
// empty folders, so there's nothing to commit; this is just disk
|
|
786
|
-
// hygiene so the file tree stops showing the empty containers.
|
|
787
|
-
//
|
|
788
|
-
// This MUST recurse: a folder that held *subfolders* still has those
|
|
789
|
-
// (now-empty) subdirectory shells on disk after the per-file deletes,
|
|
790
|
-
// so a single non-recursive `rmdir(absolute)` would see a non-empty
|
|
791
|
-
// dir and bail — leaving the folder visible in the tree and looking
|
|
792
|
-
// undeletable (BEVA-132). `removeEmptyDirs` walks bottom-up and only
|
|
793
|
-
// removes dirs that are *actually empty* at the moment it visits them,
|
|
794
|
-
// so a concurrent writer's new file (and its parent chain) is
|
|
795
|
-
// preserved — the same safety property the old non-recursive check had.
|
|
1010
|
+
// In the folder's turn: keeping a folder under this one (a file delete
|
|
1011
|
+
// racing this one) waits until the sweep is done, and then finds the
|
|
1012
|
+
// folder gone instead of writing it back.
|
|
1013
|
+
let filesInDir: string[];
|
|
796
1014
|
try {
|
|
797
|
-
await
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
1015
|
+
filesInDir = await workspaceService.withFolderTurn(id, filePath, async () => {
|
|
1016
|
+
const files = await enumerateFilesUnder(disk, absolute, workspaceDir);
|
|
1017
|
+
for (const relFile of files) {
|
|
1018
|
+
await withLock(
|
|
1019
|
+
id,
|
|
1020
|
+
user,
|
|
1021
|
+
relFile,
|
|
1022
|
+
async () => {
|
|
1023
|
+
await workspaceService.deleteFile(id, relFile);
|
|
1024
|
+
},
|
|
1025
|
+
{ skipFsTreeEvent: true },
|
|
1026
|
+
);
|
|
1027
|
+
}
|
|
1028
|
+
// No explicit push here — each per-file release enqueues a
|
|
1029
|
+
// pending-commits row, and the background worker drains them
|
|
1030
|
+
// (commit + push) on its own schedule. The N round-trips that
|
|
1031
|
+
// the old skipPush+pushBranch pattern collapsed into one happen
|
|
1032
|
+
// serially in the worker; user perception is unchanged because
|
|
1033
|
+
// the disk-side delete is what other sessions see via
|
|
1034
|
+
// `fs-tree-changed`.
|
|
1035
|
+
// This is the EXPLICIT folder delete — the one operation that removes
|
|
1036
|
+
// a folder — so the walk above deleted the placeholders too, and the
|
|
1037
|
+
// now-empty directory subtree is swept off disk. Git doesn't track
|
|
1038
|
+
// empty folders, so there's nothing more to commit; this is disk
|
|
1039
|
+
// hygiene so the file tree stops showing the deleted containers.
|
|
1040
|
+
//
|
|
1041
|
+
// This MUST recurse: a folder that held *subfolders* still has those
|
|
1042
|
+
// (now-empty) subdirectory shells on disk after the per-file deletes,
|
|
1043
|
+
// so a single non-recursive `rmdir(absolute)` would see a non-empty
|
|
1044
|
+
// dir and bail — leaving the folder visible in the tree and looking
|
|
1045
|
+
// undeletable (BEVA-132). `removeEmptyDirs` walks bottom-up and only
|
|
1046
|
+
// removes dirs that are *actually empty* at the moment it visits them,
|
|
1047
|
+
// so a concurrent writer's new file (and its parent chain) is
|
|
1048
|
+
// preserved — the same safety property the old non-recursive check had.
|
|
1049
|
+
// A folder that vanished or filled up under a concurrent writer
|
|
1050
|
+
// is passed over inside the sweep; what reaches here is a real
|
|
1051
|
+
// failure (permissions, I/O) that left the folder on disk, and
|
|
1052
|
+
// the request fails — as the agent's delete does — rather than
|
|
1053
|
+
// the response reporting gone a folder the tree still shows. The
|
|
1054
|
+
// per-file deletes have landed either way, and the next delete
|
|
1055
|
+
// of the now-empty folder either sweeps it or says why not. The
|
|
1056
|
+
// cause is logged; the answer carries no path of the host's.
|
|
1057
|
+
try {
|
|
1058
|
+
await removeEmptyDirs(absolute);
|
|
1059
|
+
} catch (rmErr) {
|
|
1060
|
+
log.warn(`dir cleanup failed for ${printable(filePath)}`, { err: rmErr });
|
|
1061
|
+
throw new Error('The files were deleted, but the empty folder could not be removed. Delete it again.');
|
|
1062
|
+
}
|
|
1063
|
+
return files;
|
|
1064
|
+
});
|
|
1065
|
+
// The folder that HELD the deleted one was not asked to go.
|
|
1066
|
+
await keepFolderOf(id, user, filePath);
|
|
1067
|
+
} finally {
|
|
1068
|
+
// Single tree-refresh signal for the whole batch (we suppressed
|
|
1069
|
+
// the per-file ones via `skipFsTreeEvent`) — sent on failure too:
|
|
1070
|
+
// a batch that stops part way, or a parent that could not be kept,
|
|
1071
|
+
// has still changed the tree.
|
|
1072
|
+
eventBus.emit({ kind: 'fs-tree-changed', workspaceId: id, branch });
|
|
806
1073
|
}
|
|
807
|
-
// Single tree-refresh signal for the whole batch (we suppressed
|
|
808
|
-
// the per-file ones via `skipFsTreeEvent`).
|
|
809
|
-
eventBus.emit({ kind: 'fs-tree-changed', workspaceId: id, branch });
|
|
810
1074
|
res.json({ status: 'deleted', count: filesInDir.length });
|
|
811
1075
|
return;
|
|
812
1076
|
}
|
|
813
1077
|
await withLock(id, user, filePath, () => workspaceService.deleteFile(id, filePath));
|
|
1078
|
+
// Deleting content is not deleting structure: an emptied folder stays.
|
|
1079
|
+
await keepFolderOf(id, user, filePath);
|
|
814
1080
|
res.json({ status: 'deleted' });
|
|
815
1081
|
} catch (err) {
|
|
816
1082
|
sendError(res, err);
|
|
@@ -820,14 +1086,69 @@ export function createWorkspaceRoutes(
|
|
|
820
1086
|
router.patch('/workspace/:id/file', async (req, res) => {
|
|
821
1087
|
const id = authenticated(req, res);
|
|
822
1088
|
if (id === null) return;
|
|
823
|
-
const
|
|
824
|
-
|
|
1089
|
+
const body = (req.body ?? {}) as { oldPath?: unknown; newPath?: unknown };
|
|
1090
|
+
const requestedOld = requestPath(body.oldPath);
|
|
1091
|
+
const requestedNew = requestPath(body.newPath);
|
|
1092
|
+
if (requestedOld === null || requestedNew === null) {
|
|
825
1093
|
res.status(400).json({ error: 'oldPath and newPath are required in body' });
|
|
826
1094
|
return;
|
|
827
1095
|
}
|
|
1096
|
+
// Both ends inside the repository before anything is judged: the platform-file
|
|
1097
|
+
// rules, the locks and the rename all read the same two paths.
|
|
1098
|
+
const oldPath = inRepo(res, requestedOld);
|
|
1099
|
+
if (oldPath === null) return;
|
|
1100
|
+
const newPath = inRepo(res, requestedNew);
|
|
1101
|
+
if (newPath === null) return;
|
|
1102
|
+
// Two spellings of one file are now ONE path, so a move can arrive with
|
|
1103
|
+
// both ends equal. `withLock` below would survive it — the inner
|
|
1104
|
+
// acquisition sees the lock the outer just took, held by this same user,
|
|
1105
|
+
// and runs straight through — but the move itself is a rename onto
|
|
1106
|
+
// itself that commits a change and tells the diff service the path was
|
|
1107
|
+
// both deleted and rewritten. It is a client mistake, so it is a 400.
|
|
1108
|
+
if (oldPath === newPath) {
|
|
1109
|
+
res.status(400).json({ error: 'oldPath and newPath must differ' });
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
828
1112
|
const user = await requireUser(req, res);
|
|
829
1113
|
if (!user) return;
|
|
830
1114
|
try {
|
|
1115
|
+
// A platform file stays in the folder the platform reads it from —
|
|
1116
|
+
// rename, move and drag all arrive here, and all three are refused.
|
|
1117
|
+
// Moving one out is not a choice to confirm: once the root has no
|
|
1118
|
+
// `access.md`, write on the root denies everyone and the move that
|
|
1119
|
+
// would undo it is the move the gate refuses.
|
|
1120
|
+
//
|
|
1121
|
+
// The single exception is that repair: an admin putting a misplaced
|
|
1122
|
+
// copy BACK. `isPlatformRestoreShape` says whether the MOVE is that
|
|
1123
|
+
// repair — it is judged on the source's name and the destination, not
|
|
1124
|
+
// on the source being a platform file where it currently sits, because
|
|
1125
|
+
// a stray `roles.yaml` in a folder is ordinary content there and is
|
|
1126
|
+
// still the copy the root is missing. `canRestorePlatformFile` then
|
|
1127
|
+
// decides who may make it, and whether the disk agrees.
|
|
1128
|
+
const oldRel = toKbRelative(oldPath, kbDirName);
|
|
1129
|
+
const newRel = toKbRelative(newPath, kbDirName);
|
|
1130
|
+
let platformRestore = false;
|
|
1131
|
+
if (oldRel !== null && newRel !== null && isPlatformRestoreShape(oldRel, newRel)) {
|
|
1132
|
+
platformRestore = await accessControl.canRestorePlatformFile(id, user.email, newRel);
|
|
1133
|
+
}
|
|
1134
|
+
// A platform file stays where the platform reads it …
|
|
1135
|
+
if (oldRel !== null && isPlatformFile(oldRel) && !platformRestore) {
|
|
1136
|
+
res.status(409).json({ error: platformFileRefusal(oldRel) });
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
// … and nothing else becomes one. `moveEntry` is a plain rename, so
|
|
1140
|
+
// without this a note renamed to `access.md` would come back as the
|
|
1141
|
+
// folder's rules, and a note dragged ONTO the root's `access.md` would
|
|
1142
|
+
// replace the rules that are there — neither of which the rule above,
|
|
1143
|
+
// which reads only the SOURCE, says anything about. The agent's move
|
|
1144
|
+
// tool has refused both since it shipped; this is the same sentence,
|
|
1145
|
+
// from the same place. Only the restore lands on a platform path, and
|
|
1146
|
+
// only where the file is missing (`canRestorePlatformFile` checked the
|
|
1147
|
+
// disk; the destination lock below checks it again, under the lock).
|
|
1148
|
+
if (newRel !== null && isPlatformFile(newRel) && !platformRestore) {
|
|
1149
|
+
res.status(409).json({ error: platformFileCreationRefusal(newRel) });
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
831
1152
|
// Move = rename on disk + commit on both sides. We lock-and-release
|
|
832
1153
|
// the destination first (commits the new file's appearance), then
|
|
833
1154
|
// lock-and-release the source path (commits its deletion). Two
|
|
@@ -848,11 +1169,46 @@ export function createWorkspaceRoutes(
|
|
|
848
1169
|
// not a single merge-style rename commit, but git's log/blame
|
|
849
1170
|
// rename detection still groups them visually after the fact.
|
|
850
1171
|
const [firstLock, secondLock] = oldPath < newPath ? [oldPath, newPath] : [newPath, oldPath];
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1172
|
+
// Only the DESTINATION side carries the restore claim. The source is an
|
|
1173
|
+
// ordinary write the caller must already hold: the exception exists so a
|
|
1174
|
+
// file can land where the platform reads it, not so an admin can take
|
|
1175
|
+
// one out of a folder that denies them.
|
|
1176
|
+
const restoreAt = (p: string) => ({
|
|
1177
|
+
platformRestore: platformRestore && p === newPath ? { source: oldPath } : undefined,
|
|
1178
|
+
});
|
|
1179
|
+
// The restore was authorised against a destination that was missing when
|
|
1180
|
+
// the access module looked. Both locks are in hand by the time this runs,
|
|
1181
|
+
// so nothing else can take that path from under the rename — but between
|
|
1182
|
+
// the two a writer still could, and a restore that lands on a platform
|
|
1183
|
+
// file replaces the rules it came to bring back. So the last thing the
|
|
1184
|
+
// move does before renaming is look again, inside the window it holds.
|
|
1185
|
+
const move = async () => {
|
|
1186
|
+
if (platformRestore) {
|
|
1187
|
+
const absoluteNew = await absoluteInRepo(id, newPath);
|
|
1188
|
+
const taken = await fs.stat(absoluteNew).then(
|
|
1189
|
+
() => true,
|
|
1190
|
+
// Genuine absence is the only "free": anything else is not an
|
|
1191
|
+
// answer, and an unanswered question does not clear the way onto
|
|
1192
|
+
// a platform file.
|
|
1193
|
+
(err: unknown) => !isAbsence(err),
|
|
1194
|
+
);
|
|
1195
|
+
if (taken) {
|
|
1196
|
+
const err: Error & { status?: number } = new Error(platformFileRefusal(newPath));
|
|
1197
|
+
err.status = 409;
|
|
1198
|
+
throw err;
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
return workspaceService.moveEntry(id, oldPath, newPath);
|
|
1202
|
+
};
|
|
1203
|
+
await withLock(
|
|
1204
|
+
id,
|
|
1205
|
+
user,
|
|
1206
|
+
firstLock,
|
|
1207
|
+
() => withLock(id, user, secondLock, move, restoreAt(secondLock)),
|
|
1208
|
+
restoreAt(firstLock),
|
|
855
1209
|
);
|
|
1210
|
+
// Moving the last entry out leaves its folder in place, like a delete.
|
|
1211
|
+
await keepFolderOf(id, user, oldPath);
|
|
856
1212
|
res.json({ status: 'moved' });
|
|
857
1213
|
} catch (err) {
|
|
858
1214
|
sendError(res, err);
|
|
@@ -862,40 +1218,89 @@ export function createWorkspaceRoutes(
|
|
|
862
1218
|
router.put('/workspace/:id/file', async (req, res) => {
|
|
863
1219
|
const id = authenticated(req, res);
|
|
864
1220
|
if (id === null) return;
|
|
865
|
-
|
|
866
|
-
|
|
1221
|
+
// ONE spelling of the target from here down. `x/a.md`, `./x/a.md` and
|
|
1222
|
+
// `x//a.md` are the same file and all pass the path validator, and this
|
|
1223
|
+
// route coordinates on that path three times over: the in-process write
|
|
1224
|
+
// turn, the workflow lock row, and the bytes themselves. Two clients
|
|
1225
|
+
// spelling one file differently would otherwise take two different locks
|
|
1226
|
+
// and both pass their own precondition.
|
|
1227
|
+
const requested = requestPath(req.query.path);
|
|
1228
|
+
if (requested === null) {
|
|
867
1229
|
res.status(400).json({ error: 'path query parameter is required' });
|
|
868
1230
|
return;
|
|
869
1231
|
}
|
|
870
|
-
const
|
|
1232
|
+
const filePath = inRepo(res, requested);
|
|
1233
|
+
if (filePath === null) return;
|
|
1234
|
+
const { content, ifAbsent, ifMatch } = req.body as {
|
|
1235
|
+
content?: string;
|
|
1236
|
+
ifAbsent?: boolean;
|
|
1237
|
+
ifMatch?: unknown;
|
|
1238
|
+
};
|
|
871
1239
|
if (content === undefined) {
|
|
872
1240
|
res.status(400).json({ error: 'content is required in body' });
|
|
873
1241
|
return;
|
|
874
1242
|
}
|
|
1243
|
+
if (ifMatch !== undefined && typeof ifMatch !== 'string') {
|
|
1244
|
+
res.status(400).json({ error: 'ifMatch, if provided, must be a string' });
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
875
1247
|
const user = await requireUser(req, res);
|
|
876
1248
|
if (!user) return;
|
|
1249
|
+
// A precondition ANSWERS A QUESTION ABOUT CONTENT, so it is gated on
|
|
1250
|
+
// READING, not writing. Write authorisation happens at `acquireLock`
|
|
1251
|
+
// inside `withLock` below, which is after the compare: without this gate
|
|
1252
|
+
// the 409-versus-403 difference is a content-equality oracle on a file the
|
|
1253
|
+
// caller may not read, and the confirming case is a write. Same gate and
|
|
1254
|
+
// same 403 as `GET /file`; a caller who may write but not read can still
|
|
1255
|
+
// save, just not ask questions about what is there.
|
|
1256
|
+
if (ifMatch !== undefined && !(await requireReadPermission(req, res, id, filePath))) return;
|
|
877
1257
|
try {
|
|
878
1258
|
// Refuse a hand-edit that would leave roles.yaml unparseable BEFORE any
|
|
879
1259
|
// byte hits disk — a broken roles.yaml is an app-wide admin lockout
|
|
880
|
-
// (loadModel hard-throws). The dedicated
|
|
1260
|
+
// (loadModel hard-throws). The dedicated App roles surface has its
|
|
881
1261
|
// own validate gate; this covers the raw-text editor path.
|
|
882
1262
|
if (isRolesYamlPath(filePath, kbDirName)) assertRolesYamlParsable(content);
|
|
883
|
-
//
|
|
884
|
-
//
|
|
885
|
-
//
|
|
886
|
-
//
|
|
887
|
-
//
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
//
|
|
892
|
-
//
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
1263
|
+
// One turn for the whole sequence, because the DECISION spans three
|
|
1264
|
+
// steps that must agree about the same file: check the precondition,
|
|
1265
|
+
// plan a creator-access grant that COMMITS, then write. A save landing
|
|
1266
|
+
// in between would leave that grant behind for a write about to be
|
|
1267
|
+
// refused, so the check is made here, before the plan, and nothing else
|
|
1268
|
+
// in this process may touch the path until the write is done. The write
|
|
1269
|
+
// inside takes the same turn re-entrantly.
|
|
1270
|
+
//
|
|
1271
|
+
// Across instances the workflow lock below is still the coordinator;
|
|
1272
|
+
// this only orders what this server is doing.
|
|
1273
|
+
await workspaceService.withPathTurn(id, filePath, async () => {
|
|
1274
|
+
// A stale `ifMatch` is refused before anything commits.
|
|
1275
|
+
if (ifMatch !== undefined) await workspaceService.assertContentMatches(id, filePath, ifMatch);
|
|
1276
|
+
// Creator read grant: a file that starts a new folder at a root the
|
|
1277
|
+
// creator cannot read would vanish from their own explorer (read is
|
|
1278
|
+
// default-deny). Plan BEFORE the write: the new folder gets its
|
|
1279
|
+
// access.md seeded first. Anywhere else the lock's read gate has
|
|
1280
|
+
// already decided — a creation the creator could not see is refused,
|
|
1281
|
+
// and one they can see needs no grant.
|
|
1282
|
+
const plan = await creatorAccess.planForCreate(id, user, filePath, 'file');
|
|
1283
|
+
if (plan?.kind === 'seed-access-md') await seedCreatorAccessMd(id, user, plan);
|
|
1284
|
+
const toWrite = content;
|
|
1285
|
+
// `ifAbsent` = exclusive create: the service's `wx` write turns a
|
|
1286
|
+
// concurrent or stale create against an existing file into a 409
|
|
1287
|
+
// instead of a silent replace. `withLock`'s failure arm releases
|
|
1288
|
+
// without committing, so the refusal leaves no trace.
|
|
1289
|
+
//
|
|
1290
|
+
// `ifMatch` = conditional write, the same 409 for a stale UPDATE: the
|
|
1291
|
+
// file must still hold the text the caller last read, re-checked at
|
|
1292
|
+
// the write itself as the decision that counts.
|
|
1293
|
+
await withLock(id, user, filePath, () =>
|
|
1294
|
+
workspaceService.writeFile(id, filePath, toWrite, {
|
|
1295
|
+
failIfExists: ifAbsent === true,
|
|
1296
|
+
expectedContent: ifMatch,
|
|
1297
|
+
}),
|
|
1298
|
+
);
|
|
1299
|
+
});
|
|
1300
|
+
// After the write, and only ever advisory: the check reports what the
|
|
1301
|
+
// saved skill names, it has no say in whether it saved.
|
|
1302
|
+
const warnings = skillSaveCheck ? await skillSaveCheck.checkSave(user.email, filePath, content) : [];
|
|
1303
|
+
res.json({ status: 'written', ...(warnings.length > 0 ? { warnings } : {}) });
|
|
899
1304
|
} catch (err) {
|
|
900
1305
|
sendError(res, err);
|
|
901
1306
|
}
|
|
@@ -904,11 +1309,17 @@ export function createWorkspaceRoutes(
|
|
|
904
1309
|
router.post('/workspace/:id/directory', async (req, res) => {
|
|
905
1310
|
const id = authenticated(req, res);
|
|
906
1311
|
if (id === null) return;
|
|
907
|
-
const { path:
|
|
908
|
-
|
|
1312
|
+
const { path: requested, defer } = req.body as { path?: string; defer?: boolean };
|
|
1313
|
+
const canonical = requestPath(requested);
|
|
1314
|
+
if (canonical === null) {
|
|
909
1315
|
res.status(400).json({ error: 'path is required in body' });
|
|
910
1316
|
return;
|
|
911
1317
|
}
|
|
1318
|
+
// The folder this call creates, as a repository path. `KnowledgeBase/Reports`
|
|
1319
|
+
// is `<kbDirName>/KnowledgeBase/Reports` — the access plan below, the
|
|
1320
|
+
// `.gitkeep` lock and the mkdir all use this one spelling.
|
|
1321
|
+
const dirPath = inRepo(res, canonical);
|
|
1322
|
+
if (dirPath === null) return;
|
|
912
1323
|
const user = await requireUser(req, res);
|
|
913
1324
|
if (!user) return;
|
|
914
1325
|
try {
|
|
@@ -951,19 +1362,45 @@ export function createWorkspaceRoutes(
|
|
|
951
1362
|
const id = authenticated(req, res);
|
|
952
1363
|
if (id === null) return;
|
|
953
1364
|
const body = (req.body ?? {}) as Record<string, unknown>;
|
|
954
|
-
const
|
|
955
|
-
const
|
|
956
|
-
if (typeof
|
|
1365
|
+
const requestedZip = body.path;
|
|
1366
|
+
const requestedDest = body.destination;
|
|
1367
|
+
if (typeof requestedZip !== 'string' || requestedZip.length === 0) {
|
|
957
1368
|
res.status(400).json({ error: 'path is required in body and must be a string' });
|
|
958
1369
|
return;
|
|
959
1370
|
}
|
|
960
|
-
if (
|
|
1371
|
+
if (requestedDest !== undefined && (typeof requestedDest !== 'string' || requestedDest.length === 0)) {
|
|
961
1372
|
res.status(400).json({ error: 'destination, if provided, must be a non-empty string' });
|
|
962
1373
|
return;
|
|
963
1374
|
}
|
|
1375
|
+
const zipPath = inRepo(res, requestedZip);
|
|
1376
|
+
if (zipPath === null) return;
|
|
1377
|
+
let destination: string | undefined;
|
|
1378
|
+
if (requestedDest !== undefined) {
|
|
1379
|
+
const normalizedDest = inRepo(res, requestedDest);
|
|
1380
|
+
if (normalizedDest === null) return;
|
|
1381
|
+
destination = normalizedDest;
|
|
1382
|
+
}
|
|
964
1383
|
const user = await requireUser(req, res);
|
|
965
1384
|
if (!user) return;
|
|
966
1385
|
try {
|
|
1386
|
+
// The destination is asked about BEFORE anything is extracted: this is
|
|
1387
|
+
// the one route whose bytes land on disk ahead of the lock, so the
|
|
1388
|
+
// lock's read gate would find them already there. Same verdict, same
|
|
1389
|
+
// refusal, one step earlier. A destination inside a folder the caller
|
|
1390
|
+
// cannot read is refused whole; a folder they can read — or a new
|
|
1391
|
+
// folder directly under a root — extracts, and each file then meets
|
|
1392
|
+
// the lock as any other write does.
|
|
1393
|
+
//
|
|
1394
|
+
// A new root folder gets its creator's access.md seeded first, as
|
|
1395
|
+
// every other creation route does: extraction would otherwise bring
|
|
1396
|
+
// the folder into existence itself, and the first file's lock would
|
|
1397
|
+
// then find an existing folder the caller cannot read.
|
|
1398
|
+
// Both spellings are already repository paths, so the inferred parent is
|
|
1399
|
+
// one too — the same destination the service resolves.
|
|
1400
|
+
const destDir = destination ?? path.posix.dirname(zipPath);
|
|
1401
|
+
const plan = await creatorAccess.planForCreate(id, user, destDir, 'dir');
|
|
1402
|
+
if (plan?.kind === 'seed-access-md') await seedCreatorAccessMd(id, user, plan);
|
|
1403
|
+
await changeGate?.assertMayChange(id, user.email, destDir, 'dir');
|
|
967
1404
|
// Extract first (all files land on disk), then sweep each extracted
|
|
968
1405
|
// file through a lock+release so it commits + pushes as its own
|
|
969
1406
|
// one-file change. Per-file commits mean the validator runs N times
|
|
@@ -971,27 +1408,18 @@ export function createWorkspaceRoutes(
|
|
|
971
1408
|
// but correct for a 100-file zip. If a single file's release fails
|
|
972
1409
|
// (e.g. validator 422), the loop stops there so the user sees the
|
|
973
1410
|
// first concrete problem rather than a list of N similar failures.
|
|
974
|
-
|
|
1411
|
+
//
|
|
1412
|
+
// Each entry is asked about before it is written, through the same
|
|
1413
|
+
// gate: an archive can carry a path into a nested folder the caller
|
|
1414
|
+
// cannot read, which the destination check above cannot see. Such an
|
|
1415
|
+
// entry is skipped and reported, never written and then refused.
|
|
1416
|
+
const result = await workspaceService.unzipFile(id, zipPath, destination, async (entryPath) => {
|
|
1417
|
+
await changeGate?.assertMayChange(id, user.email, entryPath, 'file');
|
|
1418
|
+
});
|
|
975
1419
|
for (const relFile of result.extracted) {
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
// can't read (default-deny chains), splice their read grant into
|
|
980
|
-
// its frontmatter first so the committed change already carries it.
|
|
981
|
-
// Best-effort: a failed grant write must not throw — withLock's
|
|
982
|
-
// failure path releases WITHOUT committing and would discard the
|
|
983
|
-
// extracted file from disk, turning a cosmetic grant failure into
|
|
984
|
-
// data loss.
|
|
985
|
-
try {
|
|
986
|
-
const granted = await creatorAccess.grantInExtractedFile(id, user, relFile);
|
|
987
|
-
if (granted !== null) await workspaceService.writeFile(id, relFile, granted);
|
|
988
|
-
} catch (err) {
|
|
989
|
-
console.warn(
|
|
990
|
-
`[workspace.routes] creator grant on extracted "${relFile}" failed:`,
|
|
991
|
-
err instanceof Error ? err.message : err,
|
|
992
|
-
);
|
|
993
|
-
}
|
|
994
|
-
});
|
|
1420
|
+
// The file is already on disk from the unzip; the release commits +
|
|
1421
|
+
// pushes it.
|
|
1422
|
+
await withLock(id, user, relFile, async () => undefined);
|
|
995
1423
|
}
|
|
996
1424
|
res.json(result);
|
|
997
1425
|
} catch (err) {
|
|
@@ -1002,11 +1430,16 @@ export function createWorkspaceRoutes(
|
|
|
1002
1430
|
router.post('/workspace/:id/upload', async (req, res) => {
|
|
1003
1431
|
const id = authenticated(req, res);
|
|
1004
1432
|
if (id === null) return;
|
|
1005
|
-
const
|
|
1006
|
-
if (
|
|
1433
|
+
const requested = requestPath(req.query.path);
|
|
1434
|
+
if (requested === null) {
|
|
1007
1435
|
res.status(400).json({ error: 'path query parameter is required' });
|
|
1008
1436
|
return;
|
|
1009
1437
|
}
|
|
1438
|
+
// An upload with no folder lands at the repository ROOT inside the
|
|
1439
|
+
// checkout, not beside it — eight staging test documents sat beside it
|
|
1440
|
+
// because this route took the path as written.
|
|
1441
|
+
const filePath = inRepo(res, requested);
|
|
1442
|
+
if (filePath === null) return;
|
|
1010
1443
|
// `?defer=true` is the bulk-upload caller's signal to suppress the
|
|
1011
1444
|
// per-file `fs-tree-changed` SSE — the caller emits one combined
|
|
1012
1445
|
// refresh at end-of-burst via `POST /flush`. Pushing isn't deferred
|
|
@@ -1034,21 +1467,17 @@ export function createWorkspaceRoutes(
|
|
|
1034
1467
|
chunks.push(buf);
|
|
1035
1468
|
}
|
|
1036
1469
|
const data = Buffer.concat(chunks);
|
|
1037
|
-
// Creator read grant, mirroring PUT /file:
|
|
1038
|
-
//
|
|
1039
|
-
//
|
|
1040
|
-
//
|
|
1470
|
+
// Creator read grant, mirroring PUT /file: an upload that starts a new
|
|
1471
|
+
// folder at a root seeds that folder's access.md first. An upload into
|
|
1472
|
+
// a folder the caller cannot read never gets this far — the lock's read
|
|
1473
|
+
// gate refuses it.
|
|
1041
1474
|
const plan = await creatorAccess.planForCreate(id, user, filePath, 'file');
|
|
1042
1475
|
if (plan?.kind === 'seed-access-md') await seedCreatorAccessMd(id, user, plan);
|
|
1043
|
-
const toWrite =
|
|
1044
|
-
plan?.kind === 'frontmatter'
|
|
1045
|
-
? Buffer.from(plan.apply(data.toString('utf8')), 'utf8')
|
|
1046
|
-
: data;
|
|
1047
1476
|
await withLock(
|
|
1048
1477
|
id,
|
|
1049
1478
|
user,
|
|
1050
1479
|
filePath,
|
|
1051
|
-
() => workspaceService.writeFileBinary(id, filePath,
|
|
1480
|
+
() => workspaceService.writeFileBinary(id, filePath, data),
|
|
1052
1481
|
defer ? { skipFsTreeEvent: true } : undefined,
|
|
1053
1482
|
);
|
|
1054
1483
|
res.json({ status: 'uploaded' });
|
|
@@ -1063,6 +1492,8 @@ export function createWorkspaceRoutes(
|
|
|
1063
1492
|
// happens out of band in the worker, so this route's only remaining
|
|
1064
1493
|
// responsibility is to emit the single combined `fs-tree-changed`
|
|
1065
1494
|
// event the bulk caller suppressed per file.
|
|
1495
|
+
// It takes NO path: there is nothing for the normaliser to place, which is
|
|
1496
|
+
// why this route is the one on the surface that does not call it.
|
|
1066
1497
|
router.post('/workspace/:id/flush', async (req, res) => {
|
|
1067
1498
|
const id = authenticated(req, res);
|
|
1068
1499
|
if (id === null) return;
|
|
@@ -1086,62 +1517,61 @@ export function createWorkspaceRoutes(
|
|
|
1086
1517
|
* a recursive directory delete into per-file lock+release cycles so each
|
|
1087
1518
|
* deletion lands as its own one-file change.
|
|
1088
1519
|
*
|
|
1089
|
-
* Skips
|
|
1090
|
-
*
|
|
1091
|
-
* for predictable commit sequencing.
|
|
1520
|
+
* Skips the git folder, in any spelling (`hasGitInternalsSegment`), so a
|
|
1521
|
+
* folder delete never enumerates — or deletes — the repository's git data. Returns paths in the walk's order — stable
|
|
1522
|
+
* and lexical — for predictable commit sequencing. A link counts as a file:
|
|
1523
|
+
* it is deleted as one, never followed. A folder that cannot be listed is
|
|
1524
|
+
* the delete's error: an enumeration with a hole in it would delete what it
|
|
1525
|
+
* saw and report success over what it did not.
|
|
1092
1526
|
*/
|
|
1093
|
-
async function enumerateFilesUnder(absoluteDir: string, workspaceDir: string): Promise<string[]> {
|
|
1527
|
+
async function enumerateFilesUnder(disk: ITreeWalker, absoluteDir: string, workspaceDir: string): Promise<string[]> {
|
|
1094
1528
|
const out: string[] = [];
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1529
|
+
const relOf = (dir: string, name: string) =>
|
|
1530
|
+
path.relative(workspaceDir, path.join(absoluteDir, dir, name)).replace(/\\/g, '/');
|
|
1531
|
+
try {
|
|
1532
|
+
await disk.walk(absoluteDir, { skip: (e) => hasGitInternalsSegment(e.name), unreadable: 'throw' }, [
|
|
1533
|
+
{
|
|
1534
|
+
onFile: (dir, name) => void out.push(relOf(dir, name)),
|
|
1535
|
+
onOther: (dir, e) => void out.push(relOf(dir, e.name)),
|
|
1536
|
+
},
|
|
1537
|
+
]);
|
|
1538
|
+
} catch (err) {
|
|
1539
|
+
// The operator's line carries the errno and the path; the caller's answer
|
|
1540
|
+
// names only the folder they asked about — an OS message would leak the
|
|
1541
|
+
// server's spelling of the workspace, and would not help them anyway.
|
|
1542
|
+
// Both halves of the log line are user- or disk-controlled text, so both
|
|
1543
|
+
// go through `printable`: a folder name or an OS message carrying a
|
|
1544
|
+
// control character must not steer the terminal or forge a log line.
|
|
1545
|
+
const folder = path.relative(workspaceDir, absoluteDir).replace(/\\/g, '/');
|
|
1546
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
1547
|
+
logger('workspace').error(`could not list every file under ${printable(folder)} for a delete: ${printable(reason)}`);
|
|
1548
|
+
throw Object.assign(new Error(`Could not list every file under "${folder}" — nothing was deleted. Try again, or ask an admin.`), {
|
|
1549
|
+
status: 500,
|
|
1550
|
+
});
|
|
1112
1551
|
}
|
|
1113
|
-
await walk(absoluteDir);
|
|
1114
1552
|
return out;
|
|
1115
1553
|
}
|
|
1116
1554
|
|
|
1117
1555
|
/**
|
|
1118
|
-
*
|
|
1119
|
-
*
|
|
1120
|
-
*
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1556
|
+
* The error a failed folder keep answers: the original one — its type,
|
|
1557
|
+
* status and payload decide the response — with `message` saying what
|
|
1558
|
+
* landed and what did not. Anything that is not an Error becomes one.
|
|
1559
|
+
*/
|
|
1560
|
+
function withKeptFolderContext(err: unknown, message: string): Error {
|
|
1561
|
+
if (!(err instanceof Error)) return new Error(message);
|
|
1562
|
+
err.message = message;
|
|
1563
|
+
return err;
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
/**
|
|
1567
|
+
* Whether a folder exists and holds nothing. A folder that is gone answers
|
|
1568
|
+
* false: it was deleted explicitly, and keeping it would bring it back.
|
|
1126
1569
|
*/
|
|
1127
|
-
async function
|
|
1128
|
-
let entries: import('node:fs').Dirent[];
|
|
1570
|
+
async function isEmptyFolder(absoluteDir: string): Promise<boolean> {
|
|
1129
1571
|
try {
|
|
1130
|
-
|
|
1131
|
-
} catch {
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
}
|
|
1135
|
-
for (const entry of entries) {
|
|
1136
|
-
if (entry.name === '.git' && entry.isDirectory()) continue;
|
|
1137
|
-
if (entry.isDirectory()) {
|
|
1138
|
-
await removeEmptyDirs(path.join(absoluteDir, entry.name));
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
// Re-read after pruning children: a subdir we just emptied now lets this
|
|
1142
|
-
// dir become removable too. Any surviving file (or `.git`) keeps it.
|
|
1143
|
-
const remaining = await fs.readdir(absoluteDir);
|
|
1144
|
-
if (remaining.length === 0) {
|
|
1145
|
-
await fs.rmdir(absoluteDir);
|
|
1572
|
+
return (await fs.readdir(absoluteDir)).length === 0;
|
|
1573
|
+
} catch (err) {
|
|
1574
|
+
if (isAbsence(err)) return false;
|
|
1575
|
+
throw err;
|
|
1146
1576
|
}
|
|
1147
1577
|
}
|