@bevel-software/platform-core-backend 0.14.0 → 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 +8 -8
- package/dist/core/catalog-cache-invalidation.d.ts +45 -0
- package/dist/core/catalog-cache-invalidation.d.ts.map +1 -0
- package/dist/core/catalog-cache-invalidation.js +76 -0
- package/dist/core/catalog-cache-invalidation.js.map +1 -0
- 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 +279 -64
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +66 -2
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +290 -111
- 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 +231 -15
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +83 -12
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +815 -212
- 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 +312 -84
- 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 +100 -25
- package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
- package/dist/modules/access-model/access-grammar.js +162 -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/group-files.d.ts.map +1 -1
- package/dist/modules/access-model/group-files.js +4 -1
- package/dist/modules/access-model/group-files.js.map +1 -1
- package/dist/modules/access-model/plugin-principals.d.ts +51 -0
- package/dist/modules/access-model/plugin-principals.d.ts.map +1 -0
- package/dist/modules/access-model/plugin-principals.js +119 -0
- package/dist/modules/access-model/plugin-principals.js.map +1 -0
- 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 +672 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +171 -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/index.d.ts +6 -0
- package/dist/modules/kb-sync/index.d.ts.map +1 -0
- package/dist/modules/kb-sync/index.js +5 -0
- package/dist/modules/kb-sync/index.js.map +1 -0
- package/dist/modules/kb-sync/kb-sync.interface.d.ts +77 -0
- package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -0
- package/dist/modules/kb-sync/kb-sync.interface.js +2 -0
- package/dist/modules/kb-sync/kb-sync.interface.js.map +1 -0
- package/dist/modules/kb-sync/kb-sync.routes.d.ts +70 -0
- package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -0
- package/dist/modules/kb-sync/kb-sync.routes.js +137 -0
- package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -0
- package/dist/modules/kb-sync/kb-sync.service.d.ts +45 -0
- package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -0
- package/dist/modules/kb-sync/kb-sync.service.js +185 -0
- package/dist/modules/kb-sync/kb-sync.service.js.map +1 -0
- package/dist/modules/kb-sync/sync-auth.d.ts +59 -0
- package/dist/modules/kb-sync/sync-auth.d.ts.map +1 -0
- package/dist/modules/kb-sync/sync-auth.js +62 -0
- package/dist/modules/kb-sync/sync-auth.js.map +1 -0
- package/dist/modules/kb-sync/sync-payload.d.ts +32 -0
- package/dist/modules/kb-sync/sync-payload.d.ts.map +1 -0
- package/dist/modules/kb-sync/sync-payload.js +105 -0
- package/dist/modules/kb-sync/sync-payload.js.map +1 -0
- package/dist/modules/marketplace/git-http.routes.d.ts +42 -0
- package/dist/modules/marketplace/git-http.routes.d.ts.map +1 -0
- package/dist/modules/marketplace/git-http.routes.js +192 -0
- package/dist/modules/marketplace/git-http.routes.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts +33 -0
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js +88 -0
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-codes.store.d.ts +59 -0
- package/dist/modules/marketplace/github-facade/github-facade-codes.store.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-codes.store.js +96 -0
- package/dist/modules/marketplace/github-facade/github-facade-codes.store.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts +126 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js +192 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade.routes.d.ts +47 -0
- package/dist/modules/marketplace/github-facade/github-facade.routes.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade.routes.js +252 -0
- package/dist/modules/marketplace/github-facade/github-facade.routes.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade.service.d.ts +121 -0
- package/dist/modules/marketplace/github-facade/github-facade.service.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade.service.js +213 -0
- package/dist/modules/marketplace/github-facade/github-facade.service.js.map +1 -0
- package/dist/modules/marketplace/github-facade/index.d.ts +6 -0
- package/dist/modules/marketplace/github-facade/index.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/index.js +6 -0
- package/dist/modules/marketplace/github-facade/index.js.map +1 -0
- package/dist/modules/marketplace/index.d.ts +4 -0
- package/dist/modules/marketplace/index.d.ts.map +1 -0
- package/dist/modules/marketplace/index.js +4 -0
- package/dist/modules/marketplace/index.js.map +1 -0
- package/dist/modules/marketplace/marketplace-repo.service.d.ts +135 -0
- package/dist/modules/marketplace/marketplace-repo.service.d.ts.map +1 -0
- package/dist/modules/marketplace/marketplace-repo.service.js +318 -0
- package/dist/modules/marketplace/marketplace-repo.service.js.map +1 -0
- 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 +8 -2
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts +14 -8
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js +16 -2
- package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
- package/dist/modules/mcp/oauth/oauth-state.d.ts +13 -2
- package/dist/modules/mcp/oauth/oauth-state.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/oauth-state.js +3 -1
- package/dist/modules/mcp/oauth/oauth-state.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 +104 -0
- package/dist/modules/plugins/compile/compile-marketplace.d.ts.map +1 -0
- package/dist/modules/plugins/compile/compile-marketplace.js +323 -0
- package/dist/modules/plugins/compile/compile-marketplace.js.map +1 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts +73 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts.map +1 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.js +135 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.js.map +1 -0
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.d.ts +37 -0
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js +158 -0
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js.map +1 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts +65 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.js +204 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.js.map +1 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.d.ts +39 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.js +110 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.js.map +1 -0
- package/dist/modules/plugins/discovery/native.source.d.ts +16 -0
- package/dist/modules/plugins/discovery/native.source.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/native.source.js +120 -0
- package/dist/modules/plugins/discovery/native.source.js.map +1 -0
- package/dist/modules/plugins/discovery/plugin-source.d.ts +110 -0
- package/dist/modules/plugins/discovery/plugin-source.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/plugin-source.js +2 -0
- package/dist/modules/plugins/discovery/plugin-source.js.map +1 -0
- package/dist/modules/plugins/index.d.ts +15 -3
- package/dist/modules/plugins/index.d.ts.map +1 -1
- package/dist/modules/plugins/index.js +13 -2
- 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 +69 -0
- package/dist/modules/plugins/plugin-links.d.ts.map +1 -0
- package/dist/modules/plugins/plugin-links.js +163 -0
- package/dist/modules/plugins/plugin-links.js.map +1 -0
- package/dist/modules/plugins/plugin-links.service.d.ts +111 -0
- package/dist/modules/plugins/plugin-links.service.d.ts.map +1 -0
- package/dist/modules/plugins/plugin-links.service.js +295 -0
- package/dist/modules/plugins/plugin-links.service.js.map +1 -0
- package/dist/modules/plugins/plugin-provision.service.d.ts +129 -21
- package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.js +320 -75
- package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
- package/dist/modules/plugins/plugin-rename.service.d.ts +71 -0
- package/dist/modules/plugins/plugin-rename.service.d.ts.map +1 -0
- package/dist/modules/plugins/plugin-rename.service.js +254 -0
- package/dist/modules/plugins/plugin-rename.service.js.map +1 -0
- package/dist/modules/plugins/plugins.contract.d.ts +78 -4
- package/dist/modules/plugins/plugins.contract.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +44 -5
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +297 -115
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/plugins/plugins.service.d.ts +82 -3
- package/dist/modules/plugins/plugins.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.service.js +178 -48
- 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 +157 -2
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +412 -35
- 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 +57 -1
- package/dist/modules/settings/setup.routes.d.ts.map +1 -1
- package/dist/modules/settings/setup.routes.js +408 -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 +4 -1
- package/dist/modules/skills/index.d.ts.map +1 -1
- package/dist/modules/skills/index.js +2 -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 +24 -71
- package/dist/modules/skills/pending-skills.service.js.map +1 -1
- package/dist/modules/skills/skill-access-requests.routes.d.ts +38 -0
- package/dist/modules/skills/skill-access-requests.routes.d.ts.map +1 -0
- package/dist/modules/skills/skill-access-requests.routes.js +171 -0
- package/dist/modules/skills/skill-access-requests.routes.js.map +1 -0
- package/dist/modules/skills/skills.contract.d.ts +67 -2
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.d.ts +35 -3
- package/dist/modules/skills/skills.routes.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.js +57 -4
- package/dist/modules/skills/skills.routes.js.map +1 -1
- package/dist/modules/skills/skills.service.d.ts +31 -3
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +236 -65
- 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 +66 -1
- package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.js +2 -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 +23 -4
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +79 -19
- 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 +36 -0
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.js +136 -96
- 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 +121 -2
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +415 -92
- 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 +303 -6
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +753 -170
- 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 +57 -2
- package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.js +165 -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 +217 -14
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +1142 -109
- 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 +41 -0
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.js +85 -0
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.js.map +1 -0
- 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 +174 -54
- 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 +521 -126
- 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 +657 -246
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +323 -12
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +1006 -257
- 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 +128 -0
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +166 -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-errors.d.ts +8 -0
- package/dist/shared/fs-errors.d.ts.map +1 -0
- package/dist/shared/fs-errors.js +11 -0
- package/dist/shared/fs-errors.js.map +1 -0
- package/dist/shared/fs-walk.d.ts +20 -6
- package/dist/shared/fs-walk.d.ts.map +1 -1
- package/dist/shared/fs-walk.js +35 -27
- package/dist/shared/fs-walk.js.map +1 -1
- 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/kb-walk.d.ts +52 -0
- package/dist/shared/kb-walk.d.ts.map +1 -0
- package/dist/shared/kb-walk.js +76 -0
- package/dist/shared/kb-walk.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/path-order.d.ts +18 -0
- package/dist/shared/path-order.d.ts.map +1 -0
- package/dist/shared/path-order.js +35 -0
- package/dist/shared/path-order.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/shared/ttl-cache.d.ts +28 -1
- package/dist/shared/ttl-cache.d.ts.map +1 -1
- package/dist/shared/ttl-cache.js +33 -1
- package/dist/shared/ttl-cache.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 +364 -54
- package/kb-template/KnowledgeBase/How to get started.md +1 -1
- package/kb-template/Skills/.gitkeep +0 -0
- package/kb-template/access.md +5 -3
- package/kb-template/mcp-description.md +27 -0
- package/migrations/0005_github_facade.sql +25 -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/0005_snapshot.json +1717 -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 +49 -0
- package/package.json +8 -7
- package/src/__tests__/core-config.domain.test.ts +67 -2
- package/src/__tests__/kb-layout-config.test.ts +295 -0
- package/src/__tests__/open-change-gate.ts +15 -0
- package/src/core/__tests__/catalog-cache-invalidation.test.ts +226 -0
- 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 +111 -0
- package/src/core/catalog-revision.ts +110 -0
- package/src/core/create-core-server.ts +358 -63
- package/src/core/create-core-services.ts +449 -107
- 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 +88 -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 +142 -81
- 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 +385 -0
- 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 +14 -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 +269 -18
- package/src/modules/access/access-control.service.ts +943 -226
- 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 +385 -105
- 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-grammar.test.ts +18 -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__/plugin-token-canonical.test.ts +40 -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 +198 -49
- 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/group-files.ts +4 -0
- package/src/modules/access-model/plugin-principals.ts +130 -0
- 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 +177 -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/__tests__/kb-sync.routes.test.ts +297 -0
- package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +295 -0
- package/src/modules/kb-sync/__tests__/sync-auth.test.ts +118 -0
- package/src/modules/kb-sync/__tests__/sync-payload.test.ts +148 -0
- package/src/modules/kb-sync/index.ts +19 -0
- package/src/modules/kb-sync/kb-sync.interface.ts +79 -0
- package/src/modules/kb-sync/kb-sync.routes.ts +200 -0
- package/src/modules/kb-sync/kb-sync.service.ts +204 -0
- package/src/modules/kb-sync/sync-auth.ts +118 -0
- package/src/modules/kb-sync/sync-payload.ts +143 -0
- package/src/modules/marketplace/__tests__/github-facade.test.ts +575 -0
- package/src/modules/marketplace/__tests__/marketplace-git.test.ts +180 -0
- package/src/modules/marketplace/__tests__/marketplace-repo.head.test.ts +47 -0
- package/src/modules/marketplace/git-http.routes.ts +196 -0
- package/src/modules/marketplace/github-facade/github-facade-admin.routes.ts +110 -0
- package/src/modules/marketplace/github-facade/github-facade-codes.store.ts +145 -0
- package/src/modules/marketplace/github-facade/github-facade-credentials.service.ts +263 -0
- package/src/modules/marketplace/github-facade/github-facade.routes.ts +294 -0
- package/src/modules/marketplace/github-facade/github-facade.service.ts +282 -0
- package/src/modules/marketplace/github-facade/index.ts +26 -0
- package/src/modules/marketplace/index.ts +3 -0
- package/src/modules/marketplace/marketplace-repo.service.ts +352 -0
- 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 +8 -2
- package/src/modules/mcp/oauth/oauth-consent.routes.ts +28 -6
- package/src/modules/mcp/oauth/oauth-state.ts +15 -3
- package/src/modules/mcp/surface-log-throttle.ts +120 -0
- package/src/modules/plugins/__tests__/bundle-dialect.e2e.test.ts +166 -0
- package/src/modules/plugins/__tests__/bundle-dialect.test.ts +165 -0
- package/src/modules/plugins/__tests__/compile-marketplace.test.ts +423 -0
- 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 +201 -16
- package/src/modules/plugins/__tests__/plugin-links.service.test.ts +284 -0
- package/src/modules/plugins/__tests__/plugin-names.test.ts +77 -0
- package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +330 -20
- package/src/modules/plugins/__tests__/plugin-rename.service.test.ts +456 -0
- package/src/modules/plugins/__tests__/plugins.routes.test.ts +441 -43
- 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 +500 -0
- package/src/modules/plugins/compile/marketplace-compiler.service.ts +151 -0
- package/src/modules/plugins/discovery/__tests__/kb-plugin-source.test.ts +365 -0
- package/src/modules/plugins/discovery/bundle-dialect/bundle.source.ts +161 -0
- package/src/modules/plugins/discovery/bundle-dialect/registry.ts +250 -0
- package/src/modules/plugins/discovery/kb-plugin-source.ts +117 -0
- package/src/modules/plugins/discovery/native.source.ts +140 -0
- package/src/modules/plugins/discovery/plugin-source.ts +113 -0
- package/src/modules/plugins/index.ts +25 -2
- 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 +350 -0
- package/src/modules/plugins/plugin-links.ts +187 -0
- package/src/modules/plugins/plugin-provision.service.ts +356 -78
- package/src/modules/plugins/plugin-rename.service.ts +310 -0
- package/src/modules/plugins/plugins.contract.ts +79 -4
- package/src/modules/plugins/plugins.routes.ts +320 -123
- package/src/modules/plugins/plugins.service.ts +184 -57
- 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 +240 -1
- 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.sync-status.test.ts +77 -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 +478 -39
- 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 +480 -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__/skill-access-requests.routes.test.ts +128 -0
- package/src/modules/skills/__tests__/skills.routes.warnings.test.ts +91 -0
- package/src/modules/skills/__tests__/skills.service.test.ts +308 -2
- package/src/modules/skills/allowed-tools-check.ts +352 -0
- package/src/modules/skills/index.ts +5 -0
- package/src/modules/skills/pending-skills.service.ts +26 -85
- package/src/modules/skills/skill-access-requests.routes.ts +203 -0
- package/src/modules/skills/skills.contract.ts +67 -3
- package/src/modules/skills/skills.routes.ts +75 -4
- package/src/modules/skills/skills.service.ts +258 -66
- 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 +73 -1
- package/src/modules/tool-auth/external-api-key.service.ts +95 -29
- 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-json-discovery.test.ts +24 -1
- 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 +11 -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 +164 -95
- 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 +440 -104
- 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 +3 -1
- 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 +3 -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 +296 -27
- 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 +133 -2
- package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +442 -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.postMergePull.test.ts +3 -3
- package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +47 -4
- package/src/modules/workflow/git/__tests__/git.service.revertPathsAndPush.test.ts +177 -0
- package/src/modules/workflow/git/__tests__/git.service.syncFromRemote.test.ts +253 -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 +922 -199
- package/src/modules/workflow/git/node-git-runner.ts +315 -0
- package/src/modules/workflow/git/pull-request.service.ts +188 -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 +1341 -174
- 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 +31 -13
- 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 +94 -0
- package/src/modules/workspace/__tests__/workspace.service.missing-branch.test.ts +28 -0
- 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 +98 -13
- 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 +1405 -48
- 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 +90 -0
- package/src/modules/workspace/startup/steps/roles-yaml.step.ts +6 -12
- package/src/modules/workspace/startup/steps/seed-tree.ts +177 -54
- package/src/modules/workspace/startup/steps/template-files.step.ts +581 -126
- package/src/modules/workspace/startup/steps/template-source.ts +168 -0
- package/src/modules/workspace/workspace.routes.ts +691 -269
- package/src/modules/workspace/workspace.service.ts +1071 -292
- 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__/path-order.test.ts +24 -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/__tests__/ttl-cache.test.ts +55 -0
- package/src/shared/canonical-file-identity.ts +92 -0
- package/src/shared/domain-errors.ts +178 -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/path-order.ts +33 -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/shared/ttl-cache.ts +33 -1
- 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 -42
- package/src/shared/fs-walk.ts +0 -30
- package/src/shared/hash-email.ts +0 -14
|
@@ -1,32 +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
11
|
import {
|
|
8
|
-
AGENTS_DIR,
|
|
9
|
-
DATA_DIR,
|
|
10
12
|
DEFAULT_BRANCH,
|
|
11
|
-
KNOWLEDGE_BASE_DIR,
|
|
12
13
|
KNOWLEDGE_DIR,
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
canonicalRelativePath,
|
|
15
|
+
folderPlaceholderPath,
|
|
16
|
+
isPlatformFile,
|
|
17
|
+
isPlatformRestoreShape,
|
|
18
|
+
platformFileCreationRefusal,
|
|
19
|
+
platformFileRefusal,
|
|
20
|
+
reservedRootDirNames,
|
|
15
21
|
} from '@bevel-software/platform-shared';
|
|
16
22
|
import { FolderTooLargeError, type ReadTreeFilter } from './workspace.service.js';
|
|
17
23
|
import { branchForWorkspaceId } from '../../shared/workspace-id.js';
|
|
18
|
-
import type
|
|
24
|
+
import { EntryExistsError, type WorkspaceService } from './workspace.service.js';
|
|
19
25
|
import type { AuthService } from '../auth/auth.service.js';
|
|
20
26
|
import type { IAccessControl } from '../access/access-control.interface.js';
|
|
21
27
|
import { canReadWorkspacePath, resolveReadableMap, toKbRelative } from '../access-model/kb-read-filter.js';
|
|
22
28
|
import type { ICreatorAccess } from '../access-model/creator.js';
|
|
29
|
+
import type { IChangeReadGate } from '../access-model/change-gate.js';
|
|
23
30
|
import { isRolesYamlPath, assertRolesYamlParsable } from '../access-model/roles-yaml-guard.js';
|
|
24
31
|
import type { WorkflowEventBus } from '../workflow/event-bus.js';
|
|
25
|
-
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';
|
|
26
39
|
import '../auth/auth.middleware.js'; // Express Request augmentation
|
|
40
|
+
import type { SkillSaveCheck } from './workspace.tools.js';
|
|
27
41
|
|
|
28
42
|
const MAX_UPLOAD_BYTES = 50 * 1024 * 1024; // 50 MB
|
|
29
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
|
+
|
|
30
64
|
/**
|
|
31
65
|
* Workspaces are per-branch (PLAN §3). Any authenticated user can access
|
|
32
66
|
* any branch's workspace; coordination of concurrent edits lives in the
|
|
@@ -51,8 +85,64 @@ export function createWorkspaceRoutes(
|
|
|
51
85
|
kbDirName: string,
|
|
52
86
|
creatorAccess: ICreatorAccess,
|
|
53
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,
|
|
54
98
|
): express.Router {
|
|
55
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
|
+
}
|
|
56
146
|
|
|
57
147
|
function authenticated(
|
|
58
148
|
req: express.Request,
|
|
@@ -92,12 +182,49 @@ export function createWorkspaceRoutes(
|
|
|
92
182
|
}
|
|
93
183
|
return user;
|
|
94
184
|
} catch (err) {
|
|
95
|
-
|
|
185
|
+
log.error('requireUser failed:', { err });
|
|
96
186
|
res.status(500).json({ error: 'Internal server error' });
|
|
97
187
|
return null;
|
|
98
188
|
}
|
|
99
189
|
}
|
|
100
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
|
+
|
|
101
228
|
/**
|
|
102
229
|
* Acquire the workflow lock for `(workspaceId, branch, targetPath)`, run
|
|
103
230
|
* `op`, then release. Release commits + pushes the file as a one-file
|
|
@@ -125,7 +252,17 @@ export function createWorkspaceRoutes(
|
|
|
125
252
|
* `skipPush` option is gone — under the queue model commits +
|
|
126
253
|
* pushes happen out of band in the worker, not inline here.)
|
|
127
254
|
*/
|
|
128
|
-
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
|
+
},
|
|
129
266
|
): Promise<T> {
|
|
130
267
|
const branch = branchForWorkspaceId(workspaceId);
|
|
131
268
|
// If the caller already holds the lock, do NOT acquire-and-release
|
|
@@ -161,7 +298,9 @@ export function createWorkspaceRoutes(
|
|
|
161
298
|
eventBus.emit({ kind: 'fs-tree-changed', workspaceId, branch });
|
|
162
299
|
return result;
|
|
163
300
|
}
|
|
164
|
-
const acquired = await workflowService.acquireLock(workspaceId, branch, targetPath, user
|
|
301
|
+
const acquired = await workflowService.acquireLock(workspaceId, branch, targetPath, user, {
|
|
302
|
+
platformRestore: options?.platformRestore,
|
|
303
|
+
});
|
|
165
304
|
if (!acquired.acquired) {
|
|
166
305
|
const holder = acquired.lock.holderName || 'another user';
|
|
167
306
|
const err: Error & { status?: number } = new Error(
|
|
@@ -170,16 +309,22 @@ export function createWorkspaceRoutes(
|
|
|
170
309
|
err.status = 409;
|
|
171
310
|
throw err;
|
|
172
311
|
}
|
|
173
|
-
//
|
|
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.
|
|
174
323
|
//
|
|
175
|
-
// - op() FAILED
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
// would silently persist that partial state as a real committed
|
|
180
|
-
// change. `releaseLockNoCommit` drops the lock row only —
|
|
181
|
-
// partial disk state stays where it is but never becomes a
|
|
182
|
-
// 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.
|
|
183
328
|
//
|
|
184
329
|
// - op() SUCCEEDED → release. The new releaseLock enqueues a
|
|
185
330
|
// pending-commit row (the actual `commitFile + push` runs out
|
|
@@ -193,12 +338,17 @@ export function createWorkspaceRoutes(
|
|
|
193
338
|
result = await op();
|
|
194
339
|
opSucceeded = true;
|
|
195
340
|
} catch (err) {
|
|
341
|
+
const untouched = wroteNothing(err);
|
|
196
342
|
try {
|
|
197
|
-
|
|
343
|
+
if (untouched) {
|
|
344
|
+
await workflowService.releaseLockUntouched(workspaceId, branch, targetPath, user);
|
|
345
|
+
} else {
|
|
346
|
+
await workflowService.releaseLockNoCommit(workspaceId, branch, targetPath, user);
|
|
347
|
+
}
|
|
198
348
|
} catch (releaseErr) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
349
|
+
log.warn(
|
|
350
|
+
`${untouched ? 'releaseLockUntouched' : 'releaseLockNoCommit'} failed after op error for "${targetPath}":`,
|
|
351
|
+
{ err: releaseErr },
|
|
202
352
|
);
|
|
203
353
|
}
|
|
204
354
|
throw err;
|
|
@@ -225,19 +375,65 @@ export function createWorkspaceRoutes(
|
|
|
225
375
|
return result;
|
|
226
376
|
}
|
|
227
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
|
+
|
|
228
425
|
/**
|
|
229
426
|
* Map any thrown error to an HTTP response. Centralises the response
|
|
230
427
|
* shape so each route handler stays focused on its own logic.
|
|
231
|
-
* - `WorkflowDomainError` → its `.status` + `.payload`
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
* - 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)
|
|
235
431
|
* - Anything else with a `.status` → that status
|
|
236
432
|
* - Default → 500
|
|
237
433
|
*/
|
|
238
434
|
function sendError(res: express.Response, err: unknown): void {
|
|
239
435
|
if (err instanceof WorkflowDomainError) {
|
|
240
|
-
res.status(err.status).json(
|
|
436
|
+
res.status(err.status).json(domainErrorBody(err));
|
|
241
437
|
return;
|
|
242
438
|
}
|
|
243
439
|
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
@@ -246,18 +442,6 @@ export function createWorkspaceRoutes(
|
|
|
246
442
|
res.status(status).json({ error: msg });
|
|
247
443
|
return;
|
|
248
444
|
}
|
|
249
|
-
if (msg === 'Path traversal detected') {
|
|
250
|
-
res.status(403).json({ error: msg });
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
if (msg.startsWith('Invalid path') || msg.startsWith('Only .zip')) {
|
|
254
|
-
res.status(400).json({ error: msg });
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
if (msg.startsWith('Could not read zip file')) {
|
|
258
|
-
res.status(422).json({ error: msg });
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
445
|
res.status(500).json({ error: msg });
|
|
262
446
|
}
|
|
263
447
|
|
|
@@ -290,13 +474,17 @@ export function createWorkspaceRoutes(
|
|
|
290
474
|
);
|
|
291
475
|
res.json({ workspace, fileTree });
|
|
292
476
|
} catch (error) {
|
|
477
|
+
// A typed domain answer — a branch origin no longer has (410), a name
|
|
478
|
+
// git refuses (400) — is the client's to act on, not a failure of ours;
|
|
479
|
+
// it keeps its status so the browser can say what happened.
|
|
480
|
+
if (error instanceof WorkflowDomainError) {
|
|
481
|
+
sendError(res, error);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
293
484
|
// Log the full stack so the next 500 isn't a guessing game — the
|
|
294
485
|
// bare `error.message` we returned before lost most diagnostic
|
|
295
486
|
// signal (cause chain, stack frames, error class).
|
|
296
|
-
|
|
297
|
-
`[workspace.routes] GET /workspace failed branch=${req.query.branch ?? '(default)'}:`,
|
|
298
|
-
error instanceof Error ? error.stack ?? error.message : error,
|
|
299
|
-
);
|
|
487
|
+
log.error(`GET /workspace failed branch=${req.query.branch ?? '(default)'}:`, { err: error });
|
|
300
488
|
const msg = error instanceof Error ? error.message : 'Unknown error';
|
|
301
489
|
res.status(500).json({ error: msg });
|
|
302
490
|
}
|
|
@@ -389,12 +577,11 @@ export function createWorkspaceRoutes(
|
|
|
389
577
|
let allowed: boolean;
|
|
390
578
|
try {
|
|
391
579
|
// `download` is a per-path verb in access.md, resolved via the same
|
|
392
|
-
// chain walk as `write`.
|
|
393
|
-
//
|
|
394
|
-
//
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
: 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;
|
|
398
585
|
allowed = await accessControl.canDownload(workspaceId, user.email, repoRelative);
|
|
399
586
|
} catch (err) {
|
|
400
587
|
sendError(res, err);
|
|
@@ -460,7 +647,7 @@ export function createWorkspaceRoutes(
|
|
|
460
647
|
*/
|
|
461
648
|
function buildTreeReadFilter(workspaceId: string, userEmail: string): ReadTreeFilter {
|
|
462
649
|
return async (wsRelPaths) => {
|
|
463
|
-
const verdict = await resolveReadableMap(
|
|
650
|
+
const verdict: Map<string, boolean | 'unlisted'> = await resolveReadableMap(
|
|
464
651
|
(w, e, rels) => accessControl.canReadBatch(w, e, rels),
|
|
465
652
|
workspaceId,
|
|
466
653
|
userEmail,
|
|
@@ -476,14 +663,7 @@ export function createWorkspaceRoutes(
|
|
|
476
663
|
// same treatment so legacy clones don't collapse. Only the folders
|
|
477
664
|
// themselves are forced visible — their contents stay gated by the
|
|
478
665
|
// verdict above.
|
|
479
|
-
const structuralRoots = new Set([
|
|
480
|
-
KNOWLEDGE_BASE_DIR,
|
|
481
|
-
DATA_DIR,
|
|
482
|
-
AGENTS_DIR,
|
|
483
|
-
PIPELINES_DIR,
|
|
484
|
-
PLUGINS_DIR,
|
|
485
|
-
KNOWLEDGE_DIR,
|
|
486
|
-
]);
|
|
666
|
+
const structuralRoots = new Set([...reservedRootDirNames(), KNOWLEDGE_DIR]);
|
|
487
667
|
for (const wp of wsRelPaths) {
|
|
488
668
|
const rel = toKbRelative(wp, kbDirName);
|
|
489
669
|
if (rel !== null && structuralRoots.has(rel)) verdict.set(wp, true);
|
|
@@ -492,7 +672,7 @@ export function createWorkspaceRoutes(
|
|
|
492
672
|
// `.bevelignore` is ADMIN-ONLY. It is the file that decides what the file
|
|
493
673
|
// tree and the agent view show at all, so it is deployment configuration
|
|
494
674
|
// rather than knowledge — and it sits alone in being visible: its
|
|
495
|
-
// siblings (`.gitignore`, `roles.yaml`, `access.md`,
|
|
675
|
+
// siblings (`.gitignore`, `roles.yaml`, `access.md`, the agent guide) are
|
|
496
676
|
// already hidden from every reader by the shipped ignore rules. Somebody
|
|
497
677
|
// who cannot act on it gains a puzzle; somebody who can needs to reach it.
|
|
498
678
|
//
|
|
@@ -505,9 +685,13 @@ export function createWorkspaceRoutes(
|
|
|
505
685
|
// on the default branch precisely so that editing `roles.yaml` on your
|
|
506
686
|
// own branch cannot promote you, and the same reasoning applies to
|
|
507
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.
|
|
508
692
|
const ignoreFiles = wsRelPaths.filter((wp) => path.basename(wp) === IGNORE_FILENAME);
|
|
509
693
|
if (ignoreFiles.length > 0 && !(await adminAccess.isAdmin(userEmail))) {
|
|
510
|
-
for (const wp of ignoreFiles) verdict.set(wp,
|
|
694
|
+
for (const wp of ignoreFiles) verdict.set(wp, 'unlisted');
|
|
511
695
|
}
|
|
512
696
|
return verdict;
|
|
513
697
|
};
|
|
@@ -522,44 +706,70 @@ export function createWorkspaceRoutes(
|
|
|
522
706
|
* grant on the same new directory survives. Runs BEFORE the creation itself
|
|
523
707
|
* so the explorer never shows-then-hides the new subtree; if the creation
|
|
524
708
|
* subsequently fails, the leftover is an empty new folder readable only by
|
|
525
|
-
* its creator.
|
|
526
|
-
*
|
|
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.
|
|
527
719
|
*/
|
|
528
720
|
async function seedCreatorAccessMd(
|
|
529
721
|
workspaceId: string,
|
|
530
722
|
user: AuthUser,
|
|
531
723
|
plan: { wsRelPath: string; apply: (current: string) => string },
|
|
532
724
|
): Promise<void> {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
creatorAccess.noteAccessFileWritten(workspaceId);
|
|
547
|
-
} catch (err) {
|
|
548
|
-
console.warn(
|
|
549
|
-
`[workspace.routes] creator access.md seed failed for "${plan.wsRelPath}":`,
|
|
550
|
-
err instanceof Error ? err.message : err,
|
|
551
|
-
);
|
|
552
|
-
}
|
|
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);
|
|
553
738
|
}
|
|
554
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
|
+
*/
|
|
555
763
|
router.get('/workspace/:id/file/raw', async (req, res) => {
|
|
556
764
|
const id = authenticated(req, res);
|
|
557
765
|
if (id === null) return;
|
|
558
|
-
const
|
|
559
|
-
if (
|
|
766
|
+
const requested = req.query.path;
|
|
767
|
+
if (typeof requested !== 'string' || requested.length === 0) {
|
|
560
768
|
res.status(400).json({ error: 'path query parameter is required' });
|
|
561
769
|
return;
|
|
562
770
|
}
|
|
771
|
+
const filePath = inRepo(res, requested);
|
|
772
|
+
if (filePath === null) return;
|
|
563
773
|
// `?download=1` flips this from inline-serve (used by PdfRenderer and
|
|
564
774
|
// the image renderers) to "save to disk" — and the save path is gated
|
|
565
775
|
// on per-path `download:` rules in access.md. The inline path stays
|
|
@@ -619,11 +829,18 @@ export function createWorkspaceRoutes(
|
|
|
619
829
|
`attachment; filename*=UTF-8''${encodeURIComponent(basename)}`,
|
|
620
830
|
);
|
|
621
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');
|
|
622
838
|
res.send(buffer);
|
|
623
839
|
} catch (error) {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
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);
|
|
627
844
|
return;
|
|
628
845
|
}
|
|
629
846
|
res.status(404).json({ error: 'File not found' });
|
|
@@ -658,11 +875,13 @@ export function createWorkspaceRoutes(
|
|
|
658
875
|
// Trim whitespace and strip trailing slashes — `foo/` would otherwise
|
|
659
876
|
// basename to `''` and produce a misleading `.zip` filename. Done
|
|
660
877
|
// before the empty check so `' / '` is also caught.
|
|
661
|
-
const
|
|
662
|
-
if (!
|
|
878
|
+
const trimmedPath = rawPath.trim().replace(/\/+$/, '');
|
|
879
|
+
if (!trimmedPath) {
|
|
663
880
|
res.status(400).json({ error: 'path query parameter is required' });
|
|
664
881
|
return;
|
|
665
882
|
}
|
|
883
|
+
const folderPath = inRepo(res, trimmedPath);
|
|
884
|
+
if (folderPath === null) return;
|
|
666
885
|
if (req.query.download !== '1') {
|
|
667
886
|
res.status(400).json({ error: 'download=1 is required for folder zip downloads' });
|
|
668
887
|
return;
|
|
@@ -688,11 +907,11 @@ export function createWorkspaceRoutes(
|
|
|
688
907
|
res.status(413).json({ error: error.message });
|
|
689
908
|
return;
|
|
690
909
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
res.status(403).json({ error: msg });
|
|
910
|
+
if (error instanceof PathTraversalError) {
|
|
911
|
+
sendError(res, error);
|
|
694
912
|
return;
|
|
695
913
|
}
|
|
914
|
+
const msg = error instanceof Error ? error.message : 'Unknown error';
|
|
696
915
|
if (msg === 'Not a directory') {
|
|
697
916
|
res.status(400).json({ error: msg });
|
|
698
917
|
return;
|
|
@@ -705,11 +924,13 @@ export function createWorkspaceRoutes(
|
|
|
705
924
|
router.get('/workspace/:id/file', async (req, res) => {
|
|
706
925
|
const id = authenticated(req, res);
|
|
707
926
|
if (id === null) return;
|
|
708
|
-
const
|
|
709
|
-
if (
|
|
927
|
+
const requested = requestPath(req.query.path);
|
|
928
|
+
if (requested === null) {
|
|
710
929
|
res.status(400).json({ error: 'path query parameter is required' });
|
|
711
930
|
return;
|
|
712
931
|
}
|
|
932
|
+
const filePath = inRepo(res, requested);
|
|
933
|
+
if (filePath === null) return;
|
|
713
934
|
if (!(await requireReadPermission(req, res, id, filePath))) return;
|
|
714
935
|
try {
|
|
715
936
|
const content = await workspaceService.readFile(id, filePath);
|
|
@@ -719,23 +940,46 @@ export function createWorkspaceRoutes(
|
|
|
719
940
|
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
|
|
720
941
|
res.json({ content });
|
|
721
942
|
} catch (error) {
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
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' });
|
|
725
953
|
return;
|
|
726
954
|
}
|
|
727
|
-
|
|
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." });
|
|
728
970
|
}
|
|
729
971
|
});
|
|
730
972
|
|
|
731
973
|
router.delete('/workspace/:id/file', async (req, res) => {
|
|
732
974
|
const id = authenticated(req, res);
|
|
733
975
|
if (id === null) return;
|
|
734
|
-
const
|
|
735
|
-
if (
|
|
976
|
+
const requested = requestPath(req.query.path);
|
|
977
|
+
if (requested === null) {
|
|
736
978
|
res.status(400).json({ error: 'path query parameter is required' });
|
|
737
979
|
return;
|
|
738
980
|
}
|
|
981
|
+
const filePath = inRepo(res, requested);
|
|
982
|
+
if (filePath === null) return;
|
|
739
983
|
const user = await requireUser(req, res);
|
|
740
984
|
if (!user) return;
|
|
741
985
|
try {
|
|
@@ -749,19 +993,12 @@ export function createWorkspaceRoutes(
|
|
|
749
993
|
// emits are also coalesced into a single end-of-batch event so the
|
|
750
994
|
// explorer doesn't refresh N times.
|
|
751
995
|
const workspaceDir = await workspaceService.getWorkspacePath(id);
|
|
752
|
-
|
|
753
|
-
//
|
|
754
|
-
//
|
|
755
|
-
//
|
|
756
|
-
//
|
|
757
|
-
|
|
758
|
-
// dir-delete branch operate on directories outside the workspace.
|
|
759
|
-
const workspaceRoot = path.resolve(workspaceDir);
|
|
760
|
-
if (absolute !== workspaceRoot && !absolute.startsWith(workspaceRoot + path.sep)) {
|
|
761
|
-
const err: Error & { status?: number } = new Error('Path traversal detected');
|
|
762
|
-
err.status = 403;
|
|
763
|
-
throw err;
|
|
764
|
-
}
|
|
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);
|
|
765
1002
|
let stat: { isDirectory: () => boolean } | null = null;
|
|
766
1003
|
try {
|
|
767
1004
|
stat = await fs.stat(absolute);
|
|
@@ -769,56 +1006,77 @@ export function createWorkspaceRoutes(
|
|
|
769
1006
|
// Not on disk — let workspaceService.deleteFile return its own 404.
|
|
770
1007
|
}
|
|
771
1008
|
if (stat?.isDirectory()) {
|
|
772
|
-
const filesInDir = await enumerateFilesUnder(absolute, workspaceDir);
|
|
773
1009
|
const branch = branchForWorkspaceId(id);
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
relFile,
|
|
779
|
-
async () => {
|
|
780
|
-
await workspaceService.deleteFile(id, relFile);
|
|
781
|
-
},
|
|
782
|
-
{ skipFsTreeEvent: true },
|
|
783
|
-
);
|
|
784
|
-
}
|
|
785
|
-
// No explicit push here — each per-file release enqueues a
|
|
786
|
-
// pending-commits row, and the background worker drains them
|
|
787
|
-
// (commit + push) on its own schedule. The N round-trips that
|
|
788
|
-
// the old skipPush+pushBranch pattern collapsed into one happen
|
|
789
|
-
// serially in the worker; user perception is unchanged because
|
|
790
|
-
// the disk-side delete is what other sessions see via
|
|
791
|
-
// `fs-tree-changed`.
|
|
792
|
-
// Sweep the now-empty directory subtree off disk. Git doesn't track
|
|
793
|
-
// empty folders, so there's nothing to commit; this is just disk
|
|
794
|
-
// hygiene so the file tree stops showing the empty containers.
|
|
795
|
-
//
|
|
796
|
-
// This MUST recurse: a folder that held *subfolders* still has those
|
|
797
|
-
// (now-empty) subdirectory shells on disk after the per-file deletes,
|
|
798
|
-
// so a single non-recursive `rmdir(absolute)` would see a non-empty
|
|
799
|
-
// dir and bail — leaving the folder visible in the tree and looking
|
|
800
|
-
// undeletable (BEVA-132). `removeEmptyDirs` walks bottom-up and only
|
|
801
|
-
// removes dirs that are *actually empty* at the moment it visits them,
|
|
802
|
-
// so a concurrent writer's new file (and its parent chain) is
|
|
803
|
-
// 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[];
|
|
804
1014
|
try {
|
|
805
|
-
await
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
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 });
|
|
814
1073
|
}
|
|
815
|
-
// Single tree-refresh signal for the whole batch (we suppressed
|
|
816
|
-
// the per-file ones via `skipFsTreeEvent`).
|
|
817
|
-
eventBus.emit({ kind: 'fs-tree-changed', workspaceId: id, branch });
|
|
818
1074
|
res.json({ status: 'deleted', count: filesInDir.length });
|
|
819
1075
|
return;
|
|
820
1076
|
}
|
|
821
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);
|
|
822
1080
|
res.json({ status: 'deleted' });
|
|
823
1081
|
} catch (err) {
|
|
824
1082
|
sendError(res, err);
|
|
@@ -828,14 +1086,69 @@ export function createWorkspaceRoutes(
|
|
|
828
1086
|
router.patch('/workspace/:id/file', async (req, res) => {
|
|
829
1087
|
const id = authenticated(req, res);
|
|
830
1088
|
if (id === null) return;
|
|
831
|
-
const
|
|
832
|
-
|
|
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) {
|
|
833
1093
|
res.status(400).json({ error: 'oldPath and newPath are required in body' });
|
|
834
1094
|
return;
|
|
835
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
|
+
}
|
|
836
1112
|
const user = await requireUser(req, res);
|
|
837
1113
|
if (!user) return;
|
|
838
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
|
+
}
|
|
839
1152
|
// Move = rename on disk + commit on both sides. We lock-and-release
|
|
840
1153
|
// the destination first (commits the new file's appearance), then
|
|
841
1154
|
// lock-and-release the source path (commits its deletion). Two
|
|
@@ -856,11 +1169,46 @@ export function createWorkspaceRoutes(
|
|
|
856
1169
|
// not a single merge-style rename commit, but git's log/blame
|
|
857
1170
|
// rename detection still groups them visually after the fact.
|
|
858
1171
|
const [firstLock, secondLock] = oldPath < newPath ? [oldPath, newPath] : [newPath, oldPath];
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
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),
|
|
863
1209
|
);
|
|
1210
|
+
// Moving the last entry out leaves its folder in place, like a delete.
|
|
1211
|
+
await keepFolderOf(id, user, oldPath);
|
|
864
1212
|
res.json({ status: 'moved' });
|
|
865
1213
|
} catch (err) {
|
|
866
1214
|
sendError(res, err);
|
|
@@ -870,40 +1218,89 @@ export function createWorkspaceRoutes(
|
|
|
870
1218
|
router.put('/workspace/:id/file', async (req, res) => {
|
|
871
1219
|
const id = authenticated(req, res);
|
|
872
1220
|
if (id === null) return;
|
|
873
|
-
|
|
874
|
-
|
|
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) {
|
|
875
1229
|
res.status(400).json({ error: 'path query parameter is required' });
|
|
876
1230
|
return;
|
|
877
1231
|
}
|
|
878
|
-
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
|
+
};
|
|
879
1239
|
if (content === undefined) {
|
|
880
1240
|
res.status(400).json({ error: 'content is required in body' });
|
|
881
1241
|
return;
|
|
882
1242
|
}
|
|
1243
|
+
if (ifMatch !== undefined && typeof ifMatch !== 'string') {
|
|
1244
|
+
res.status(400).json({ error: 'ifMatch, if provided, must be a string' });
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
883
1247
|
const user = await requireUser(req, res);
|
|
884
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;
|
|
885
1257
|
try {
|
|
886
1258
|
// Refuse a hand-edit that would leave roles.yaml unparseable BEFORE any
|
|
887
1259
|
// byte hits disk — a broken roles.yaml is an app-wide admin lockout
|
|
888
|
-
// (loadModel hard-throws). The dedicated
|
|
1260
|
+
// (loadModel hard-throws). The dedicated App roles surface has its
|
|
889
1261
|
// own validate gate; this covers the raw-text editor path.
|
|
890
1262
|
if (isRolesYamlPath(filePath, kbDirName)) assertRolesYamlParsable(content);
|
|
891
|
-
//
|
|
892
|
-
//
|
|
893
|
-
//
|
|
894
|
-
//
|
|
895
|
-
//
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
//
|
|
900
|
-
//
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
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 } : {}) });
|
|
907
1304
|
} catch (err) {
|
|
908
1305
|
sendError(res, err);
|
|
909
1306
|
}
|
|
@@ -912,11 +1309,17 @@ export function createWorkspaceRoutes(
|
|
|
912
1309
|
router.post('/workspace/:id/directory', async (req, res) => {
|
|
913
1310
|
const id = authenticated(req, res);
|
|
914
1311
|
if (id === null) return;
|
|
915
|
-
const { path:
|
|
916
|
-
|
|
1312
|
+
const { path: requested, defer } = req.body as { path?: string; defer?: boolean };
|
|
1313
|
+
const canonical = requestPath(requested);
|
|
1314
|
+
if (canonical === null) {
|
|
917
1315
|
res.status(400).json({ error: 'path is required in body' });
|
|
918
1316
|
return;
|
|
919
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;
|
|
920
1323
|
const user = await requireUser(req, res);
|
|
921
1324
|
if (!user) return;
|
|
922
1325
|
try {
|
|
@@ -959,19 +1362,45 @@ export function createWorkspaceRoutes(
|
|
|
959
1362
|
const id = authenticated(req, res);
|
|
960
1363
|
if (id === null) return;
|
|
961
1364
|
const body = (req.body ?? {}) as Record<string, unknown>;
|
|
962
|
-
const
|
|
963
|
-
const
|
|
964
|
-
if (typeof
|
|
1365
|
+
const requestedZip = body.path;
|
|
1366
|
+
const requestedDest = body.destination;
|
|
1367
|
+
if (typeof requestedZip !== 'string' || requestedZip.length === 0) {
|
|
965
1368
|
res.status(400).json({ error: 'path is required in body and must be a string' });
|
|
966
1369
|
return;
|
|
967
1370
|
}
|
|
968
|
-
if (
|
|
1371
|
+
if (requestedDest !== undefined && (typeof requestedDest !== 'string' || requestedDest.length === 0)) {
|
|
969
1372
|
res.status(400).json({ error: 'destination, if provided, must be a non-empty string' });
|
|
970
1373
|
return;
|
|
971
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
|
+
}
|
|
972
1383
|
const user = await requireUser(req, res);
|
|
973
1384
|
if (!user) return;
|
|
974
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');
|
|
975
1404
|
// Extract first (all files land on disk), then sweep each extracted
|
|
976
1405
|
// file through a lock+release so it commits + pushes as its own
|
|
977
1406
|
// one-file change. Per-file commits mean the validator runs N times
|
|
@@ -979,27 +1408,18 @@ export function createWorkspaceRoutes(
|
|
|
979
1408
|
// but correct for a 100-file zip. If a single file's release fails
|
|
980
1409
|
// (e.g. validator 422), the loop stops there so the user sees the
|
|
981
1410
|
// first concrete problem rather than a list of N similar failures.
|
|
982
|
-
|
|
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
|
+
});
|
|
983
1419
|
for (const relFile of result.extracted) {
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
// can't read (default-deny chains), splice their read grant into
|
|
988
|
-
// its frontmatter first so the committed change already carries it.
|
|
989
|
-
// Best-effort: a failed grant write must not throw — withLock's
|
|
990
|
-
// failure path releases WITHOUT committing and would discard the
|
|
991
|
-
// extracted file from disk, turning a cosmetic grant failure into
|
|
992
|
-
// data loss.
|
|
993
|
-
try {
|
|
994
|
-
const granted = await creatorAccess.grantInExtractedFile(id, user, relFile);
|
|
995
|
-
if (granted !== null) await workspaceService.writeFile(id, relFile, granted);
|
|
996
|
-
} catch (err) {
|
|
997
|
-
console.warn(
|
|
998
|
-
`[workspace.routes] creator grant on extracted "${relFile}" failed:`,
|
|
999
|
-
err instanceof Error ? err.message : err,
|
|
1000
|
-
);
|
|
1001
|
-
}
|
|
1002
|
-
});
|
|
1420
|
+
// The file is already on disk from the unzip; the release commits +
|
|
1421
|
+
// pushes it.
|
|
1422
|
+
await withLock(id, user, relFile, async () => undefined);
|
|
1003
1423
|
}
|
|
1004
1424
|
res.json(result);
|
|
1005
1425
|
} catch (err) {
|
|
@@ -1010,11 +1430,16 @@ export function createWorkspaceRoutes(
|
|
|
1010
1430
|
router.post('/workspace/:id/upload', async (req, res) => {
|
|
1011
1431
|
const id = authenticated(req, res);
|
|
1012
1432
|
if (id === null) return;
|
|
1013
|
-
const
|
|
1014
|
-
if (
|
|
1433
|
+
const requested = requestPath(req.query.path);
|
|
1434
|
+
if (requested === null) {
|
|
1015
1435
|
res.status(400).json({ error: 'path query parameter is required' });
|
|
1016
1436
|
return;
|
|
1017
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;
|
|
1018
1443
|
// `?defer=true` is the bulk-upload caller's signal to suppress the
|
|
1019
1444
|
// per-file `fs-tree-changed` SSE — the caller emits one combined
|
|
1020
1445
|
// refresh at end-of-burst via `POST /flush`. Pushing isn't deferred
|
|
@@ -1042,21 +1467,17 @@ export function createWorkspaceRoutes(
|
|
|
1042
1467
|
chunks.push(buf);
|
|
1043
1468
|
}
|
|
1044
1469
|
const data = Buffer.concat(chunks);
|
|
1045
|
-
// Creator read grant, mirroring PUT /file:
|
|
1046
|
-
//
|
|
1047
|
-
//
|
|
1048
|
-
//
|
|
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.
|
|
1049
1474
|
const plan = await creatorAccess.planForCreate(id, user, filePath, 'file');
|
|
1050
1475
|
if (plan?.kind === 'seed-access-md') await seedCreatorAccessMd(id, user, plan);
|
|
1051
|
-
const toWrite =
|
|
1052
|
-
plan?.kind === 'frontmatter'
|
|
1053
|
-
? Buffer.from(plan.apply(data.toString('utf8')), 'utf8')
|
|
1054
|
-
: data;
|
|
1055
1476
|
await withLock(
|
|
1056
1477
|
id,
|
|
1057
1478
|
user,
|
|
1058
1479
|
filePath,
|
|
1059
|
-
() => workspaceService.writeFileBinary(id, filePath,
|
|
1480
|
+
() => workspaceService.writeFileBinary(id, filePath, data),
|
|
1060
1481
|
defer ? { skipFsTreeEvent: true } : undefined,
|
|
1061
1482
|
);
|
|
1062
1483
|
res.json({ status: 'uploaded' });
|
|
@@ -1071,6 +1492,8 @@ export function createWorkspaceRoutes(
|
|
|
1071
1492
|
// happens out of band in the worker, so this route's only remaining
|
|
1072
1493
|
// responsibility is to emit the single combined `fs-tree-changed`
|
|
1073
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.
|
|
1074
1497
|
router.post('/workspace/:id/flush', async (req, res) => {
|
|
1075
1498
|
const id = authenticated(req, res);
|
|
1076
1499
|
if (id === null) return;
|
|
@@ -1094,62 +1517,61 @@ export function createWorkspaceRoutes(
|
|
|
1094
1517
|
* a recursive directory delete into per-file lock+release cycles so each
|
|
1095
1518
|
* deletion lands as its own one-file change.
|
|
1096
1519
|
*
|
|
1097
|
-
* Skips
|
|
1098
|
-
*
|
|
1099
|
-
* 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.
|
|
1100
1526
|
*/
|
|
1101
|
-
async function enumerateFilesUnder(absoluteDir: string, workspaceDir: string): Promise<string[]> {
|
|
1527
|
+
async function enumerateFilesUnder(disk: ITreeWalker, absoluteDir: string, workspaceDir: string): Promise<string[]> {
|
|
1102
1528
|
const out: string[] = [];
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
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
|
+
});
|
|
1120
1551
|
}
|
|
1121
|
-
await walk(absoluteDir);
|
|
1122
1552
|
return out;
|
|
1123
1553
|
}
|
|
1124
1554
|
|
|
1125
1555
|
/**
|
|
1126
|
-
*
|
|
1127
|
-
*
|
|
1128
|
-
*
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
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.
|
|
1134
1569
|
*/
|
|
1135
|
-
async function
|
|
1136
|
-
let entries: import('node:fs').Dirent[];
|
|
1570
|
+
async function isEmptyFolder(absoluteDir: string): Promise<boolean> {
|
|
1137
1571
|
try {
|
|
1138
|
-
|
|
1139
|
-
} catch {
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
}
|
|
1143
|
-
for (const entry of entries) {
|
|
1144
|
-
if (entry.name === '.git' && entry.isDirectory()) continue;
|
|
1145
|
-
if (entry.isDirectory()) {
|
|
1146
|
-
await removeEmptyDirs(path.join(absoluteDir, entry.name));
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
// Re-read after pruning children: a subdir we just emptied now lets this
|
|
1150
|
-
// dir become removable too. Any surviving file (or `.git`) keeps it.
|
|
1151
|
-
const remaining = await fs.readdir(absoluteDir);
|
|
1152
|
-
if (remaining.length === 0) {
|
|
1153
|
-
await fs.rmdir(absoluteDir);
|
|
1572
|
+
return (await fs.readdir(absoluteDir)).length === 0;
|
|
1573
|
+
} catch (err) {
|
|
1574
|
+
if (isAbsence(err)) return false;
|
|
1575
|
+
throw err;
|
|
1154
1576
|
}
|
|
1155
1577
|
}
|