@bevel-software/platform-core-backend 0.15.1 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD-PARTY-NOTICES.md +4 -4
- package/dist/core/catalog-cache-invalidation.d.ts +16 -0
- package/dist/core/catalog-cache-invalidation.d.ts.map +1 -1
- package/dist/core/catalog-cache-invalidation.js +6 -1
- package/dist/core/catalog-cache-invalidation.js.map +1 -1
- package/dist/core/catalog-revision.d.ts +39 -0
- package/dist/core/catalog-revision.d.ts.map +1 -0
- package/dist/core/catalog-revision.js +94 -0
- package/dist/core/catalog-revision.js.map +1 -0
- package/dist/core/create-core-server.d.ts +7 -0
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +199 -75
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +49 -2
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +212 -86
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +138 -0
- package/dist/core/lifecycle.d.ts.map +1 -0
- package/dist/core/lifecycle.js +218 -0
- package/dist/core/lifecycle.js.map +1 -0
- package/dist/core/public-config.d.ts +82 -0
- package/dist/core/public-config.d.ts.map +1 -0
- package/dist/core/public-config.js +78 -0
- package/dist/core/public-config.js.map +1 -0
- package/dist/core/readiness.d.ts +86 -0
- package/dist/core/readiness.d.ts.map +1 -0
- package/dist/core/readiness.js +56 -0
- package/dist/core/readiness.js.map +1 -0
- package/dist/core-config.d.ts +30 -0
- package/dist/core-config.d.ts.map +1 -1
- package/dist/core-config.js +64 -2
- package/dist/core-config.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +215 -11
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +57 -11
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +616 -155
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access-mutation.service.d.ts +71 -5
- package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
- package/dist/modules/access/access-mutation.service.js +161 -13
- package/dist/modules/access/access-mutation.service.js.map +1 -1
- package/dist/modules/access/access-view.d.ts +147 -0
- package/dist/modules/access/access-view.d.ts.map +1 -0
- package/dist/modules/access/access-view.js +211 -0
- package/dist/modules/access/access-view.js.map +1 -0
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +255 -79
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/admin-locked-commit.d.ts +2 -3
- package/dist/modules/access/admin-locked-commit.d.ts.map +1 -1
- package/dist/modules/access/admin-locked-commit.js +9 -8
- package/dist/modules/access/admin-locked-commit.js.map +1 -1
- package/dist/modules/access/admin-route-helpers.d.ts.map +1 -1
- package/dist/modules/access/admin-route-helpers.js +4 -4
- package/dist/modules/access/admin-route-helpers.js.map +1 -1
- package/dist/modules/access/change-read-gate.d.ts +53 -0
- package/dist/modules/access/change-read-gate.d.ts.map +1 -0
- package/dist/modules/access/change-read-gate.js +134 -0
- package/dist/modules/access/change-read-gate.js.map +1 -0
- package/dist/modules/access/creator-access.d.ts +33 -39
- package/dist/modules/access/creator-access.d.ts.map +1 -1
- package/dist/modules/access/creator-access.js +92 -119
- package/dist/modules/access/creator-access.js.map +1 -1
- package/dist/modules/access/directory-sync-bot.d.ts.map +1 -1
- package/dist/modules/access/directory-sync-bot.js +3 -1
- package/dist/modules/access/directory-sync-bot.js.map +1 -1
- package/dist/modules/access/groups-admin.service.d.ts +3 -3
- package/dist/modules/access/groups-admin.service.js +2 -2
- package/dist/modules/access/reference-scan.d.ts +1 -1
- package/dist/modules/access/reference-scan.d.ts.map +1 -1
- package/dist/modules/access/reference-scan.js +5 -4
- package/dist/modules/access/reference-scan.js.map +1 -1
- package/dist/modules/access/roles-admin.service.d.ts +72 -13
- package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
- package/dist/modules/access/roles-admin.service.js +71 -13
- package/dist/modules/access/roles-admin.service.js.map +1 -1
- package/dist/modules/access/synced-groups-committer.d.ts.map +1 -1
- package/dist/modules/access/synced-groups-committer.js +8 -6
- package/dist/modules/access/synced-groups-committer.js.map +1 -1
- package/dist/modules/access/synced-groups-writer.d.ts.map +1 -1
- package/dist/modules/access/synced-groups-writer.js +3 -8
- package/dist/modules/access/synced-groups-writer.js.map +1 -1
- package/dist/modules/access/user-access-removal.service.d.ts +209 -0
- package/dist/modules/access/user-access-removal.service.d.ts.map +1 -0
- package/dist/modules/access/user-access-removal.service.js +662 -0
- package/dist/modules/access/user-access-removal.service.js.map +1 -0
- package/dist/modules/access-model/access-errors.d.ts +32 -1
- package/dist/modules/access-model/access-errors.d.ts.map +1 -1
- package/dist/modules/access-model/access-errors.js +38 -11
- package/dist/modules/access-model/access-errors.js.map +1 -1
- package/dist/modules/access-model/access-grammar.d.ts +49 -24
- package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
- package/dist/modules/access-model/access-grammar.js +68 -48
- package/dist/modules/access-model/access-grammar.js.map +1 -1
- package/dist/modules/access-model/access-splice.d.ts.map +1 -1
- package/dist/modules/access-model/access-splice.js +18 -18
- package/dist/modules/access-model/access-splice.js.map +1 -1
- package/dist/modules/access-model/access-template.d.ts +22 -0
- package/dist/modules/access-model/access-template.d.ts.map +1 -0
- package/dist/modules/access-model/access-template.js +42 -0
- package/dist/modules/access-model/access-template.js.map +1 -0
- package/dist/modules/access-model/change-gate.d.ts +65 -0
- package/dist/modules/access-model/change-gate.d.ts.map +1 -0
- package/dist/modules/access-model/change-gate.js +25 -0
- package/dist/modules/access-model/change-gate.js.map +1 -0
- package/dist/modules/access-model/creator.d.ts +13 -16
- package/dist/modules/access-model/creator.d.ts.map +1 -1
- package/dist/modules/access-model/creator.js.map +1 -1
- package/dist/modules/access-model/frontmatter-lines.d.ts +27 -0
- package/dist/modules/access-model/frontmatter-lines.d.ts.map +1 -0
- package/dist/modules/access-model/frontmatter-lines.js +74 -0
- package/dist/modules/access-model/frontmatter-lines.js.map +1 -0
- package/dist/modules/access-model/plugin-principals.d.ts +5 -3
- package/dist/modules/access-model/plugin-principals.d.ts.map +1 -1
- package/dist/modules/access-model/plugin-principals.js +11 -17
- package/dist/modules/access-model/plugin-principals.js.map +1 -1
- package/dist/modules/access-model/roles-yaml-guard.d.ts +73 -2
- package/dist/modules/access-model/roles-yaml-guard.d.ts.map +1 -1
- package/dist/modules/access-model/roles-yaml-guard.js +153 -3
- package/dist/modules/access-model/roles-yaml-guard.js.map +1 -1
- package/dist/modules/admin/admin-access.service.d.ts.map +1 -1
- package/dist/modules/admin/admin-access.service.js +5 -2
- package/dist/modules/admin/admin-access.service.js.map +1 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +21 -0
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -0
- package/dist/modules/agent-instructions/agent-instructions.routes.js +38 -0
- package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -0
- package/dist/modules/agent-instructions/compose.d.ts +71 -0
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -0
- package/dist/modules/agent-instructions/compose.js +234 -0
- package/dist/modules/agent-instructions/compose.js.map +1 -0
- package/dist/modules/agent-instructions/index.d.ts +4 -0
- package/dist/modules/agent-instructions/index.d.ts.map +1 -0
- package/dist/modules/agent-instructions/index.js +4 -0
- package/dist/modules/agent-instructions/index.js.map +1 -0
- package/dist/modules/agent-instructions/read-preamble.d.ts +37 -0
- package/dist/modules/agent-instructions/read-preamble.d.ts.map +1 -0
- package/dist/modules/agent-instructions/read-preamble.js +81 -0
- package/dist/modules/agent-instructions/read-preamble.js.map +1 -0
- package/dist/modules/auth/account-erasure.service.d.ts +30 -4
- package/dist/modules/auth/account-erasure.service.d.ts.map +1 -1
- package/dist/modules/auth/account-erasure.service.js +96 -10
- package/dist/modules/auth/account-erasure.service.js.map +1 -1
- package/dist/modules/auth/account.routes.d.ts +6 -3
- package/dist/modules/auth/account.routes.d.ts.map +1 -1
- package/dist/modules/auth/account.routes.js +109 -10
- package/dist/modules/auth/account.routes.js.map +1 -1
- package/dist/modules/auth/auth.routes.d.ts +10 -3
- package/dist/modules/auth/auth.routes.d.ts.map +1 -1
- package/dist/modules/auth/auth.routes.js +9 -4
- package/dist/modules/auth/auth.routes.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +89 -5
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +160 -25
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.d.ts +46 -3
- package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.js +99 -21
- package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.js +14 -2
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.d.ts.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.js +56 -7
- package/dist/modules/connection-probe/connection-probe.service.js.map +1 -1
- package/dist/modules/database/advisory-lock.d.ts +103 -0
- package/dist/modules/database/advisory-lock.d.ts.map +1 -0
- package/dist/modules/database/advisory-lock.js +320 -0
- package/dist/modules/database/advisory-lock.js.map +1 -0
- package/dist/modules/database/connection.d.ts.map +1 -1
- package/dist/modules/database/connection.js +9 -1
- package/dist/modules/database/connection.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +346 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +119 -0
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/database/migrate.d.ts.map +1 -1
- package/dist/modules/database/migrate.js +26 -6
- package/dist/modules/database/migrate.js.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.d.ts.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.js +4 -2
- package/dist/modules/declared-variables/declared-variables.routes.js.map +1 -1
- package/dist/modules/diff/diff.routes.d.ts.map +1 -1
- package/dist/modules/diff/diff.routes.js +2 -4
- package/dist/modules/diff/diff.routes.js.map +1 -1
- package/dist/modules/diff/diff.service.d.ts +4 -1
- package/dist/modules/diff/diff.service.d.ts.map +1 -1
- package/dist/modules/diff/diff.service.js +79 -81
- package/dist/modules/diff/diff.service.js.map +1 -1
- package/dist/modules/kb-fs/bevel-ignore.d.ts +35 -0
- package/dist/modules/kb-fs/bevel-ignore.d.ts.map +1 -0
- package/dist/modules/kb-fs/bevel-ignore.js +71 -0
- package/dist/modules/kb-fs/bevel-ignore.js.map +1 -0
- package/dist/modules/kb-fs/file-change-notifier.d.ts.map +1 -1
- package/dist/modules/kb-fs/file-change-notifier.js +3 -1
- package/dist/modules/kb-fs/file-change-notifier.js.map +1 -1
- package/dist/modules/kb-fs/git-guarded-filesystem.d.ts +37 -0
- package/dist/modules/kb-fs/git-guarded-filesystem.d.ts.map +1 -0
- package/dist/modules/kb-fs/git-guarded-filesystem.js +104 -0
- package/dist/modules/kb-fs/git-guarded-filesystem.js.map +1 -0
- package/dist/modules/kb-fs/locking-filesystem.d.ts +113 -18
- package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.js +353 -76
- package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
- package/dist/modules/kb-fs/node-fs.d.ts +23 -0
- package/dist/modules/kb-fs/node-fs.d.ts.map +1 -0
- package/dist/modules/kb-fs/node-fs.js +158 -0
- package/dist/modules/kb-fs/node-fs.js.map +1 -0
- package/dist/modules/kb-fs/read-only-filesystem.d.ts +10 -9
- package/dist/modules/kb-fs/read-only-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/read-only-filesystem.js +18 -9
- package/dist/modules/kb-fs/read-only-filesystem.js.map +1 -1
- package/dist/modules/kb-fs/repo-path.d.ts +100 -3
- package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
- package/dist/modules/kb-fs/repo-path.js +195 -3
- package/dist/modules/kb-fs/repo-path.js.map +1 -1
- package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.routes.js +4 -2
- package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -1
- package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.service.js +7 -5
- package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
- package/dist/modules/marketplace/git-http.routes.d.ts.map +1 -1
- package/dist/modules/marketplace/git-http.routes.js +6 -4
- package/dist/modules/marketplace/git-http.routes.js.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts +12 -2
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js +32 -3
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts +27 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js +45 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade.routes.d.ts.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade.routes.js +30 -6
- package/dist/modules/marketplace/github-facade/github-facade.routes.js.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade.service.d.ts +21 -9
- package/dist/modules/marketplace/github-facade/github-facade.service.d.ts.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade.service.js +60 -19
- package/dist/modules/marketplace/github-facade/github-facade.service.js.map +1 -1
- package/dist/modules/marketplace/github-facade/index.d.ts +1 -1
- package/dist/modules/marketplace/github-facade/index.d.ts.map +1 -1
- package/dist/modules/marketplace/github-facade/index.js +1 -1
- package/dist/modules/marketplace/github-facade/index.js.map +1 -1
- package/dist/modules/marketplace/marketplace-repo.service.d.ts +14 -1
- package/dist/modules/marketplace/marketplace-repo.service.d.ts.map +1 -1
- package/dist/modules/marketplace/marketplace-repo.service.js +26 -8
- package/dist/modules/marketplace/marketplace-repo.service.js.map +1 -1
- package/dist/modules/mcp/downstream-pool.d.ts +107 -0
- package/dist/modules/mcp/downstream-pool.d.ts.map +1 -0
- package/dist/modules/mcp/downstream-pool.js +221 -0
- package/dist/modules/mcp/downstream-pool.js.map +1 -0
- package/dist/modules/mcp/downstream-token-refresh.d.ts +77 -0
- package/dist/modules/mcp/downstream-token-refresh.d.ts.map +1 -0
- package/dist/modules/mcp/downstream-token-refresh.js +166 -0
- package/dist/modules/mcp/downstream-token-refresh.js.map +1 -0
- package/dist/modules/mcp/first-call-probe.cli-args.d.ts +78 -0
- package/dist/modules/mcp/first-call-probe.cli-args.d.ts.map +1 -0
- package/dist/modules/mcp/first-call-probe.cli-args.js +151 -0
- package/dist/modules/mcp/first-call-probe.cli-args.js.map +1 -0
- package/dist/modules/mcp/first-call-probe.cli.d.ts +2 -0
- package/dist/modules/mcp/first-call-probe.cli.d.ts.map +1 -0
- package/dist/modules/mcp/first-call-probe.cli.js +62 -0
- package/dist/modules/mcp/first-call-probe.cli.js.map +1 -0
- package/dist/modules/mcp/first-call-probe.d.ts +165 -0
- package/dist/modules/mcp/first-call-probe.d.ts.map +1 -0
- package/dist/modules/mcp/first-call-probe.js +298 -0
- package/dist/modules/mcp/first-call-probe.js.map +1 -0
- package/dist/modules/mcp/manual-failure-memo.d.ts +42 -17
- package/dist/modules/mcp/manual-failure-memo.d.ts.map +1 -1
- package/dist/modules/mcp/manual-failure-memo.js +79 -23
- package/dist/modules/mcp/manual-failure-memo.js.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.d.ts +9 -5
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +24 -16
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/mcp.routes.d.ts +4 -4
- package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.routes.js +83 -179
- package/dist/modules/mcp/mcp.routes.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +218 -61
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +713 -159
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +4 -2
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js +3 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
- package/dist/modules/mcp/surface-log-throttle.d.ts +59 -0
- package/dist/modules/mcp/surface-log-throttle.d.ts.map +1 -0
- package/dist/modules/mcp/surface-log-throttle.js +94 -0
- package/dist/modules/mcp/surface-log-throttle.js.map +1 -0
- package/dist/modules/plugins/compile/compile-marketplace.d.ts +13 -6
- package/dist/modules/plugins/compile/compile-marketplace.d.ts.map +1 -1
- package/dist/modules/plugins/compile/compile-marketplace.js +119 -36
- package/dist/modules/plugins/compile/compile-marketplace.js.map +1 -1
- package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts +8 -1
- package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts.map +1 -1
- package/dist/modules/plugins/compile/marketplace-compiler.service.js +9 -6
- package/dist/modules/plugins/compile/marketplace-compiler.service.js.map +1 -1
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.d.ts.map +1 -1
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js +32 -9
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js.map +1 -1
- package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts +26 -6
- package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts.map +1 -1
- package/dist/modules/plugins/discovery/bundle-dialect/registry.js +71 -14
- package/dist/modules/plugins/discovery/bundle-dialect/registry.js.map +1 -1
- package/dist/modules/plugins/discovery/kb-plugin-source.d.ts +4 -2
- package/dist/modules/plugins/discovery/kb-plugin-source.d.ts.map +1 -1
- package/dist/modules/plugins/discovery/kb-plugin-source.js +9 -6
- package/dist/modules/plugins/discovery/kb-plugin-source.js.map +1 -1
- package/dist/modules/plugins/discovery/native.source.d.ts +2 -1
- package/dist/modules/plugins/discovery/native.source.d.ts.map +1 -1
- package/dist/modules/plugins/discovery/native.source.js +22 -4
- package/dist/modules/plugins/discovery/native.source.js.map +1 -1
- package/dist/modules/plugins/discovery/plugin-source.d.ts +17 -3
- package/dist/modules/plugins/discovery/plugin-source.d.ts.map +1 -1
- package/dist/modules/plugins/index.d.ts +6 -3
- package/dist/modules/plugins/index.d.ts.map +1 -1
- package/dist/modules/plugins/index.js +6 -3
- package/dist/modules/plugins/index.js.map +1 -1
- package/dist/modules/plugins/join-request-jobs.service.d.ts +250 -0
- package/dist/modules/plugins/join-request-jobs.service.d.ts.map +1 -0
- package/dist/modules/plugins/join-request-jobs.service.js +602 -0
- package/dist/modules/plugins/join-request-jobs.service.js.map +1 -0
- package/dist/modules/plugins/join-request-records.store.d.ts +161 -0
- package/dist/modules/plugins/join-request-records.store.d.ts.map +1 -0
- package/dist/modules/plugins/join-request-records.store.js +179 -0
- package/dist/modules/plugins/join-request-records.store.js.map +1 -0
- package/dist/modules/plugins/join-requests.service.d.ts.map +1 -1
- package/dist/modules/plugins/join-requests.service.js +4 -2
- package/dist/modules/plugins/join-requests.service.js.map +1 -1
- package/dist/modules/plugins/plugin-links.d.ts +4 -1
- package/dist/modules/plugins/plugin-links.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-links.js +6 -3
- package/dist/modules/plugins/plugin-links.js.map +1 -1
- package/dist/modules/plugins/plugin-links.service.d.ts +2 -5
- package/dist/modules/plugins/plugin-links.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-links.service.js +4 -13
- package/dist/modules/plugins/plugin-links.service.js.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.d.ts +91 -19
- package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.js +183 -38
- package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
- package/dist/modules/plugins/plugin-rename.service.d.ts +3 -1
- package/dist/modules/plugins/plugin-rename.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-rename.service.js +20 -11
- package/dist/modules/plugins/plugin-rename.service.js.map +1 -1
- package/dist/modules/plugins/plugins.contract.d.ts +52 -4
- package/dist/modules/plugins/plugins.contract.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +38 -5
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +190 -116
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/plugins/plugins.service.d.ts +38 -6
- package/dist/modules/plugins/plugins.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.service.js +86 -17
- package/dist/modules/plugins/plugins.service.js.map +1 -1
- package/dist/modules/plugins/plugins.tools.d.ts +25 -0
- package/dist/modules/plugins/plugins.tools.d.ts.map +1 -0
- package/dist/modules/plugins/plugins.tools.js +82 -0
- package/dist/modules/plugins/plugins.tools.js.map +1 -0
- package/dist/modules/plugins/teams.routes.d.ts +42 -0
- package/dist/modules/plugins/teams.routes.d.ts.map +1 -0
- package/dist/modules/plugins/teams.routes.js +79 -0
- package/dist/modules/plugins/teams.routes.js.map +1 -0
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +40 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.js +202 -37
- package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
- package/dist/modules/secrets-vault/index.d.ts +1 -1
- package/dist/modules/secrets-vault/index.d.ts.map +1 -1
- package/dist/modules/secrets-vault/index.js.map +1 -1
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.d.ts.map +1 -1
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js +5 -3
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-variable-loader.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-variable-loader.js +3 -1
- package/dist/modules/secrets-vault/secrets-variable-loader.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +78 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.js +40 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.js +106 -52
- package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
- package/dist/modules/settings/connection-check.d.ts +118 -0
- package/dist/modules/settings/connection-check.d.ts.map +1 -0
- package/dist/modules/settings/connection-check.js +287 -0
- package/dist/modules/settings/connection-check.js.map +1 -0
- package/dist/modules/settings/deployment-settings.service.d.ts +153 -7
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +348 -51
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/settings/git-root-folders.d.ts +54 -0
- package/dist/modules/settings/git-root-folders.d.ts.map +1 -0
- package/dist/modules/settings/git-root-folders.js +130 -0
- package/dist/modules/settings/git-root-folders.js.map +1 -0
- package/dist/modules/settings/oidc-check.d.ts +85 -0
- package/dist/modules/settings/oidc-check.d.ts.map +1 -0
- package/dist/modules/settings/oidc-check.js +172 -0
- package/dist/modules/settings/oidc-check.js.map +1 -0
- package/dist/modules/settings/setup.routes.d.ts +31 -1
- package/dist/modules/settings/setup.routes.d.ts.map +1 -1
- package/dist/modules/settings/setup.routes.js +400 -108
- package/dist/modules/settings/setup.routes.js.map +1 -1
- package/dist/modules/skills/allowed-tools-check.d.ts +132 -0
- package/dist/modules/skills/allowed-tools-check.d.ts.map +1 -0
- package/dist/modules/skills/allowed-tools-check.js +260 -0
- package/dist/modules/skills/allowed-tools-check.js.map +1 -0
- package/dist/modules/skills/index.d.ts +3 -1
- package/dist/modules/skills/index.d.ts.map +1 -1
- package/dist/modules/skills/index.js +1 -0
- package/dist/modules/skills/index.js.map +1 -1
- package/dist/modules/skills/pending-skills.service.d.ts +6 -11
- package/dist/modules/skills/pending-skills.service.d.ts.map +1 -1
- package/dist/modules/skills/pending-skills.service.js +18 -70
- package/dist/modules/skills/pending-skills.service.js.map +1 -1
- package/dist/modules/skills/skill-access-requests.routes.d.ts.map +1 -1
- package/dist/modules/skills/skill-access-requests.routes.js +9 -6
- package/dist/modules/skills/skill-access-requests.routes.js.map +1 -1
- package/dist/modules/skills/skills.contract.d.ts +44 -10
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.d.ts +8 -1
- package/dist/modules/skills/skills.routes.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.js +16 -2
- package/dist/modules/skills/skills.routes.js.map +1 -1
- package/dist/modules/skills/skills.service.d.ts +31 -7
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +191 -81
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts +4 -1
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +41 -6
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-auth/connection-key-rejection.d.ts +18 -0
- package/dist/modules/tool-auth/connection-key-rejection.d.ts.map +1 -0
- package/dist/modules/tool-auth/connection-key-rejection.js +20 -0
- package/dist/modules/tool-auth/connection-key-rejection.js.map +1 -0
- package/dist/modules/tool-auth/connection-keys-admin.routes.d.ts +14 -0
- package/dist/modules/tool-auth/connection-keys-admin.routes.d.ts.map +1 -0
- package/dist/modules/tool-auth/connection-keys-admin.routes.js +67 -0
- package/dist/modules/tool-auth/connection-keys-admin.routes.js.map +1 -0
- package/dist/modules/tool-auth/external-api-key.interface.d.ts +51 -3
- package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.d.ts +16 -6
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +61 -18
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.d.ts +5 -2
- package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.js +5 -2
- package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
- package/dist/modules/tool-auth/key-or-session.middleware.d.ts +23 -0
- package/dist/modules/tool-auth/key-or-session.middleware.d.ts.map +1 -0
- package/dist/modules/tool-auth/key-or-session.middleware.js +39 -0
- package/dist/modules/tool-auth/key-or-session.middleware.js.map +1 -0
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts +7 -1
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +8 -5
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/tool-context.d.ts +14 -0
- package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-context.js +28 -6
- package/dist/modules/tool-helpers/tool-context.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.js +22 -5
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts +6 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.js +7 -1
- package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
- package/dist/modules/tool-manuals/index.d.ts +2 -1
- package/dist/modules/tool-manuals/index.d.ts.map +1 -1
- package/dist/modules/tool-manuals/index.js +1 -0
- package/dist/modules/tool-manuals/index.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.js +8 -6
- package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts +10 -2
- package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.js +12 -15
- package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -1
- package/dist/modules/tool-manuals/pending-tools.service.d.ts +50 -0
- package/dist/modules/tool-manuals/pending-tools.service.d.ts.map +1 -0
- package/dist/modules/tool-manuals/pending-tools.service.js +149 -0
- package/dist/modules/tool-manuals/pending-tools.service.js.map +1 -0
- package/dist/modules/tool-manuals/tool-delete.service.d.ts +135 -0
- package/dist/modules/tool-manuals/tool-delete.service.d.ts.map +1 -0
- package/dist/modules/tool-manuals/tool-delete.service.js +303 -0
- package/dist/modules/tool-manuals/tool-delete.service.js.map +1 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +161 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.js +9 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +19 -2
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.js +127 -57
- package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +119 -5
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +404 -74
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +78 -6
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +88 -54
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/event-bus.d.ts +10 -4
- package/dist/modules/workflow/event-bus.d.ts.map +1 -1
- package/dist/modules/workflow/event-bus.js +14 -11
- package/dist/modules/workflow/event-bus.js.map +1 -1
- package/dist/modules/workflow/events.routes.d.ts +17 -2
- package/dist/modules/workflow/events.routes.d.ts.map +1 -1
- package/dist/modules/workflow/events.routes.js +31 -12
- package/dist/modules/workflow/events.routes.js.map +1 -1
- package/dist/modules/workflow/file-lock.service.d.ts +22 -4
- package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
- package/dist/modules/workflow/file-lock.service.js +27 -4
- package/dist/modules/workflow/file-lock.service.js.map +1 -1
- package/dist/modules/workflow/git/change-request-link.d.ts +20 -0
- package/dist/modules/workflow/git/change-request-link.d.ts.map +1 -0
- package/dist/modules/workflow/git/change-request-link.js +34 -0
- package/dist/modules/workflow/git/change-request-link.js.map +1 -0
- package/dist/modules/workflow/git/git-version.d.ts.map +1 -1
- package/dist/modules/workflow/git/git-version.js +3 -1
- package/dist/modules/workflow/git/git-version.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +263 -4
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +528 -118
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/node-git-runner.d.ts +36 -0
- package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -0
- package/dist/modules/workflow/git/node-git-runner.js +273 -0
- package/dist/modules/workflow/git/node-git-runner.js.map +1 -0
- package/dist/modules/workflow/git/pull-request.service.d.ts +51 -2
- package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.js +157 -25
- package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.service.d.ts +11 -0
- package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.service.js +24 -4
- package/dist/modules/workflow/pending-commits.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.d.ts +15 -8
- package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.js +107 -58
- package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
- package/dist/modules/workflow/recovery-bot.d.ts.map +1 -1
- package/dist/modules/workflow/recovery-bot.js +3 -1
- package/dist/modules/workflow/recovery-bot.js.map +1 -1
- package/dist/modules/workflow/review-workflow/approval-lock.d.ts +60 -0
- package/dist/modules/workflow/review-workflow/approval-lock.d.ts.map +1 -0
- package/dist/modules/workflow/review-workflow/approval-lock.js +116 -0
- package/dist/modules/workflow/review-workflow/approval-lock.js.map +1 -0
- package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts +54 -7
- package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts +64 -0
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js +269 -90
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
- package/dist/modules/workflow/sanitize-error.d.ts +5 -2
- package/dist/modules/workflow/sanitize-error.d.ts.map +1 -1
- package/dist/modules/workflow/sanitize-error.js +8 -4
- package/dist/modules/workflow/sanitize-error.js.map +1 -1
- package/dist/modules/workflow/workflow.routes.d.ts +2 -0
- package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.routes.js +259 -29
- package/dist/modules/workflow/workflow.routes.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +166 -14
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +906 -106
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/empty-dirs.d.ts +15 -0
- package/dist/modules/workspace/empty-dirs.d.ts.map +1 -0
- package/dist/modules/workspace/empty-dirs.js +90 -0
- package/dist/modules/workspace/empty-dirs.js.map +1 -0
- package/dist/modules/workspace/file-readers/content-mode.d.ts +50 -0
- package/dist/modules/workspace/file-readers/content-mode.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/content-mode.js +55 -0
- package/dist/modules/workspace/file-readers/content-mode.js.map +1 -0
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts +4 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.js +4 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.js.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.types.d.ts +5 -2
- package/dist/modules/workspace/file-readers/doc-extract.types.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.types.js +7 -4
- package/dist/modules/workspace/file-readers/doc-extract.types.js.map +1 -1
- package/dist/modules/workspace/file-readers/document-reader.d.ts +2 -0
- package/dist/modules/workspace/file-readers/document-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/document-reader.js +5 -0
- package/dist/modules/workspace/file-readers/document-reader.js.map +1 -1
- package/dist/modules/workspace/file-readers/extract-docx.d.ts +6 -0
- package/dist/modules/workspace/file-readers/extract-docx.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/extract-docx.js +6 -0
- package/dist/modules/workspace/file-readers/extract-docx.js.map +1 -1
- package/dist/modules/workspace/file-readers/file-reader.d.ts +24 -5
- package/dist/modules/workspace/file-readers/file-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/file-reader.js +11 -6
- package/dist/modules/workspace/file-readers/file-reader.js.map +1 -1
- package/dist/modules/workspace/file-readers/file-reader.registry.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/file-reader.registry.js +6 -1
- package/dist/modules/workspace/file-readers/file-reader.registry.js.map +1 -1
- package/dist/modules/workspace/file-readers/image-reader.d.ts +7 -5
- package/dist/modules/workspace/file-readers/image-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/image-reader.js +9 -5
- package/dist/modules/workspace/file-readers/image-reader.js.map +1 -1
- package/dist/modules/workspace/file-readers/ooxml-text.d.ts +3 -0
- package/dist/modules/workspace/file-readers/ooxml-text.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/ooxml-text.js +39 -2
- package/dist/modules/workspace/file-readers/ooxml-text.js.map +1 -1
- package/dist/modules/workspace/file-readers/text-reader.d.ts +29 -1
- package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/text-reader.js +57 -3
- package/dist/modules/workspace/file-readers/text-reader.js.map +1 -1
- package/dist/modules/workspace/git-internals.middleware.d.ts +28 -0
- package/dist/modules/workspace/git-internals.middleware.d.ts.map +1 -0
- package/dist/modules/workspace/git-internals.middleware.js +93 -0
- package/dist/modules/workspace/git-internals.middleware.js.map +1 -0
- package/dist/modules/workspace/not-found.d.ts +80 -0
- package/dist/modules/workspace/not-found.d.ts.map +1 -0
- package/dist/modules/workspace/not-found.js +112 -0
- package/dist/modules/workspace/not-found.js.map +1 -0
- package/dist/modules/workspace/session-ontology.gate.d.ts +1 -1
- package/dist/modules/workspace/session-ontology.gate.d.ts.map +1 -1
- package/dist/modules/workspace/session-ontology.gate.js +1 -1
- package/dist/modules/workspace/session-ontology.gate.js.map +1 -1
- package/dist/modules/workspace/startup/beside-checkout.d.ts +42 -0
- package/dist/modules/workspace/startup/beside-checkout.d.ts.map +1 -0
- package/dist/modules/workspace/startup/beside-checkout.js +110 -0
- package/dist/modules/workspace/startup/beside-checkout.js.map +1 -0
- package/dist/modules/workspace/startup/kb-git.d.ts +7 -12
- package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-git.js +34 -35
- package/dist/modules/workspace/startup/kb-git.js.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts +95 -0
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.js +257 -55
- package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts +45 -0
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js +417 -385
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/personal-spaces.step.d.ts +48 -0
- package/dist/modules/workspace/startup/steps/personal-spaces.step.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/personal-spaces.step.js +183 -0
- package/dist/modules/workspace/startup/steps/personal-spaces.step.js.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.d.ts +51 -0
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.js +158 -0
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.js.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-layout.d.ts +64 -0
- package/dist/modules/workspace/startup/steps/plugin-layout.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-layout.js +97 -0
- package/dist/modules/workspace/startup/steps/plugin-layout.js.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts +7 -1
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.js +45 -81
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts +3 -1
- package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/roles-yaml.step.js +4 -12
- package/dist/modules/workspace/startup/steps/roles-yaml.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts +6 -1
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js +154 -78
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts +96 -24
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.js +446 -138
- package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.d.ts +94 -0
- package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/template-source.js +160 -0
- package/dist/modules/workspace/startup/steps/template-source.js.map +1 -0
- package/dist/modules/workspace/workspace.routes.d.ts +14 -2
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +649 -238
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +290 -12
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +924 -269
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +42 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +1590 -146
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/modules/workspace/write-denial.d.ts +76 -0
- package/dist/modules/workspace/write-denial.d.ts.map +1 -0
- package/dist/modules/workspace/write-denial.js +138 -0
- package/dist/modules/workspace/write-denial.js.map +1 -0
- package/dist/shared/canonical-file-identity.d.ts +64 -0
- package/dist/shared/canonical-file-identity.d.ts.map +1 -0
- package/dist/shared/canonical-file-identity.js +92 -0
- package/dist/shared/canonical-file-identity.js.map +1 -0
- package/dist/shared/domain-errors.d.ts +108 -0
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +137 -0
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/email-identity.d.ts +32 -0
- package/dist/shared/email-identity.d.ts.map +1 -0
- package/dist/shared/email-identity.js +37 -0
- package/dist/shared/email-identity.js.map +1 -0
- package/dist/shared/fs.contract.d.ts +206 -0
- package/dist/shared/fs.contract.d.ts.map +1 -0
- package/dist/shared/fs.contract.js +79 -0
- package/dist/shared/fs.contract.js.map +1 -0
- package/dist/shared/git-failure.d.ts +80 -0
- package/dist/shared/git-failure.d.ts.map +1 -0
- package/dist/shared/git-failure.js +139 -0
- package/dist/shared/git-failure.js.map +1 -0
- package/dist/shared/git-internals.d.ts +17 -0
- package/dist/shared/git-internals.d.ts.map +1 -0
- package/dist/shared/git-internals.js +123 -0
- package/dist/shared/git-internals.js.map +1 -0
- package/dist/shared/git.contract.d.ts +157 -0
- package/dist/shared/git.contract.d.ts.map +1 -0
- package/dist/shared/git.contract.js +103 -0
- package/dist/shared/git.contract.js.map +1 -0
- package/dist/shared/http-errors.d.ts +33 -0
- package/dist/shared/http-errors.d.ts.map +1 -0
- package/dist/shared/http-errors.js +35 -0
- package/dist/shared/http-errors.js.map +1 -0
- package/dist/shared/logger.contract.d.ts +63 -0
- package/dist/shared/logger.contract.d.ts.map +1 -0
- package/dist/shared/logger.contract.js +126 -0
- package/dist/shared/logger.contract.js.map +1 -0
- package/dist/shared/logging.d.ts +31 -0
- package/dist/shared/logging.d.ts.map +1 -0
- package/dist/shared/logging.js +92 -0
- package/dist/shared/logging.js.map +1 -0
- package/dist/shared/path-containment.d.ts +21 -0
- package/dist/shared/path-containment.d.ts.map +1 -0
- package/dist/shared/path-containment.js +34 -0
- package/dist/shared/path-containment.js.map +1 -0
- package/dist/shared/pending-proposals.d.ts +71 -0
- package/dist/shared/pending-proposals.d.ts.map +1 -0
- package/dist/shared/pending-proposals.js +98 -0
- package/dist/shared/pending-proposals.js.map +1 -0
- package/dist/shared/printable.d.ts +22 -0
- package/dist/shared/printable.d.ts.map +1 -0
- package/dist/shared/printable.js +65 -0
- package/dist/shared/printable.js.map +1 -0
- package/dist/shared/redact-secret.d.ts +30 -0
- package/dist/shared/redact-secret.d.ts.map +1 -0
- package/dist/shared/redact-secret.js +114 -0
- package/dist/shared/redact-secret.js.map +1 -0
- package/dist/shared/rename-no-replace.d.ts +119 -0
- package/dist/shared/rename-no-replace.d.ts.map +1 -0
- package/dist/shared/rename-no-replace.js +323 -0
- package/dist/shared/rename-no-replace.js.map +1 -0
- package/dist/shared/ssrf.d.ts.map +1 -1
- package/dist/shared/ssrf.js +49 -18
- package/dist/shared/ssrf.js.map +1 -1
- package/dist/version.d.ts +10 -6
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +10 -6
- package/dist/version.js.map +1 -1
- package/kb-template/.bevelignore +4 -3
- package/kb-template/AGENTS.md +281 -36
- package/kb-template/KnowledgeBase/How to get started.md +1 -1
- package/kb-template/access.md +5 -3
- package/kb-template/mcp-description.md +27 -0
- package/migrations/0006_api_tokens_revoked_by.sql +1 -0
- package/migrations/0007_github_facade_registered.sql +1 -0
- package/migrations/0008_change_request_apply_failure.sql +4 -0
- package/migrations/0009_change_request_apply_failure_kind.sql +13 -0
- package/migrations/0010_plugin_join_requests.sql +15 -0
- package/migrations/0011_plugin_join_request_claim.sql +11 -0
- package/migrations/meta/0006_snapshot.json +1723 -0
- package/migrations/meta/0007_snapshot.json +1729 -0
- package/migrations/meta/0008_snapshot.json +1753 -0
- package/migrations/meta/0009_snapshot.json +1759 -0
- package/migrations/meta/0010_snapshot.json +1872 -0
- package/migrations/meta/0011_snapshot.json +1884 -0
- package/migrations/meta/_journal.json +42 -0
- package/package.json +6 -5
- package/src/__tests__/core-config.domain.test.ts +67 -2
- package/src/__tests__/kb-layout-config.test.ts +186 -1
- package/src/__tests__/open-change-gate.ts +15 -0
- package/src/core/__tests__/catalog-cache-invalidation.test.ts +83 -3
- package/src/core/__tests__/catalog-revision.test.ts +190 -0
- package/src/core/__tests__/lifecycle.test.ts +369 -0
- package/src/core/__tests__/public-config.test.ts +30 -0
- package/src/core/__tests__/readiness.test.ts +109 -0
- package/src/core/catalog-cache-invalidation.ts +20 -1
- package/src/core/catalog-revision.ts +110 -0
- package/src/core/create-core-server.ts +258 -79
- package/src/core/create-core-services.ts +302 -81
- package/src/core/lifecycle.ts +312 -0
- package/src/core/public-config.ts +86 -0
- package/src/core/readiness.ts +123 -0
- package/src/core-config.ts +70 -4
- package/src/index.ts +15 -0
- package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +362 -0
- package/src/modules/access/__tests__/access-control.atref-batch.test.ts +2 -1
- package/src/modules/access/__tests__/access-control.atref-cache.test.ts +46 -22
- package/src/modules/access/__tests__/access-control.deny-dependents.test.ts +215 -0
- package/src/modules/access/__tests__/access-control.download-read.test.ts +268 -0
- package/src/modules/access/__tests__/access-control.everyone-read.test.ts +145 -0
- package/src/modules/access/__tests__/access-control.group-read.test.ts +163 -0
- package/src/modules/access/__tests__/access-control.invalidate-race.test.ts +2 -0
- package/src/modules/access/__tests__/access-control.platform-restore.test.ts +152 -0
- package/src/modules/access/__tests__/access-control.prospective.test.ts +189 -0
- package/src/modules/access/__tests__/access-control.read-batch.test.ts +2 -1
- package/src/modules/access/__tests__/access-control.service.test.ts +123 -78
- package/src/modules/access/__tests__/access-groups.test.ts +47 -14
- package/src/modules/access/__tests__/access-md-format.test.ts +2 -1
- package/src/modules/access/__tests__/access-mutation.service.test.ts +52 -4
- package/src/modules/access/__tests__/access-own-read-grant.test.ts +2 -0
- package/src/modules/access/__tests__/access-personal-plugin.test.ts +153 -0
- package/src/modules/access/__tests__/access-plugin-principals.test.ts +18 -1
- package/src/modules/access/__tests__/access-restrict-here.test.ts +450 -0
- package/src/modules/access/__tests__/access.routes.batch.test.ts +139 -0
- package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +394 -0
- package/src/modules/access/__tests__/access.routes.group-grant.test.ts +6 -2
- package/src/modules/access/__tests__/access.routes.overrides.test.ts +3 -2
- package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +234 -0
- package/src/modules/access/__tests__/access.routes.prospective.test.ts +216 -0
- package/src/modules/access/__tests__/access.routes.revoke.test.ts +7 -2
- package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +360 -0
- package/src/modules/access/__tests__/change-read-gate.test.ts +272 -0
- package/src/modules/access/__tests__/creator-access.test.ts +81 -83
- package/src/modules/access/__tests__/grant-sources.test.ts +3 -2
- package/src/modules/access/__tests__/groups-admin.service.test.ts +6 -5
- package/src/modules/access/__tests__/roles-admin.security.test.ts +196 -0
- package/src/modules/access/__tests__/roles-admin.service.test.ts +127 -2
- package/src/modules/access/__tests__/roles-capabilities.test.ts +5 -4
- package/src/modules/access/__tests__/roles.routes.test.ts +5 -2
- package/src/modules/access/__tests__/user-access-removal.service.test.ts +402 -0
- package/src/modules/access/__tests__/users-db-double.ts +63 -0
- package/src/modules/access/access-control.interface.ts +251 -11
- package/src/modules/access/access-control.service.ts +719 -164
- package/src/modules/access/access-mutation.service.ts +182 -13
- package/src/modules/access/access-view.ts +316 -0
- package/src/modules/access/access.routes.ts +293 -95
- package/src/modules/access/admin-locked-commit.ts +12 -14
- package/src/modules/access/admin-route-helpers.ts +4 -4
- package/src/modules/access/change-read-gate.ts +148 -0
- package/src/modules/access/creator-access.ts +89 -124
- package/src/modules/access/directory-sync-bot.ts +4 -1
- package/src/modules/access/groups-admin.service.ts +3 -3
- package/src/modules/access/reference-scan.ts +5 -5
- package/src/modules/access/roles-admin.service.ts +94 -15
- package/src/modules/access/synced-groups-committer.ts +10 -15
- package/src/modules/access/synced-groups-writer.ts +3 -12
- package/src/modules/access/user-access-removal.service.ts +711 -0
- package/src/modules/access-model/__tests__/access-splice.test.ts +63 -1
- package/src/modules/access-model/__tests__/frontmatter-fences.test.ts +71 -0
- package/src/modules/access-model/__tests__/roles-yaml-guard.test.ts +131 -0
- package/src/modules/access-model/access-errors.ts +56 -15
- package/src/modules/access-model/access-grammar.ts +85 -48
- package/src/modules/access-model/access-splice.ts +18 -18
- package/src/modules/access-model/access-template.ts +44 -0
- package/src/modules/access-model/change-gate.ts +80 -0
- package/src/modules/access-model/creator.ts +18 -21
- package/src/modules/access-model/frontmatter-lines.ts +98 -0
- package/src/modules/access-model/plugin-principals.ts +12 -19
- package/src/modules/access-model/roles-yaml-guard.ts +182 -4
- package/src/modules/admin/admin-access.service.ts +6 -2
- package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +100 -0
- package/src/modules/agent-instructions/__tests__/compose.test.ts +256 -0
- package/src/modules/agent-instructions/__tests__/read-preamble.test.ts +135 -0
- package/src/modules/agent-instructions/agent-instructions.routes.ts +41 -0
- package/src/modules/agent-instructions/compose.ts +271 -0
- package/src/modules/agent-instructions/index.ts +14 -0
- package/src/modules/agent-instructions/read-preamble.ts +88 -0
- package/src/modules/auth/__tests__/account-erasure.approval-gate.test.ts +209 -0
- package/src/modules/auth/__tests__/account.routes.test.ts +178 -2
- package/src/modules/auth/__tests__/auth.middleware.test.ts +78 -0
- package/src/modules/auth/__tests__/auth.service.test.ts +338 -5
- package/src/modules/auth/__tests__/oidc-auth-provider.test.ts +352 -12
- package/src/modules/auth/__tests__/onboarding-done.route.test.ts +3 -1
- package/src/modules/auth/account-erasure.service.ts +117 -10
- package/src/modules/auth/account.routes.ts +115 -14
- package/src/modules/auth/auth.routes.ts +20 -7
- package/src/modules/auth/auth.service.ts +187 -28
- package/src/modules/auth/oidc-auth-provider.ts +120 -22
- package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +109 -1
- package/src/modules/code-mode/code-mode.tool.ts +13 -2
- package/src/modules/connection-probe/__tests__/connection-probe.service.test.ts +78 -0
- package/src/modules/connection-probe/connection-probe.service.ts +59 -7
- package/src/modules/database/__tests__/advisory-lease.test.ts +152 -0
- package/src/modules/database/__tests__/advisory-lock.test.ts +147 -0
- package/src/modules/database/advisory-lock.ts +346 -0
- package/src/modules/database/connection.ts +9 -1
- package/src/modules/database/core-schema.ts +123 -0
- package/src/modules/database/migrate.ts +27 -6
- package/src/modules/declared-variables/declared-variables.routes.ts +6 -3
- package/src/modules/diff/__tests__/diff.service.seed-atomicity.test.ts +3 -0
- package/src/modules/diff/__tests__/diff.service.test.ts +3 -0
- package/src/modules/diff/diff.routes.ts +2 -4
- package/src/modules/diff/diff.service.ts +75 -79
- package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +472 -9
- package/src/modules/kb-fs/__tests__/node-fs.test.ts +409 -0
- package/src/modules/kb-fs/__tests__/repo-path.test.ts +210 -1
- package/src/modules/{workspace → kb-fs}/bevel-ignore.ts +22 -15
- package/src/modules/kb-fs/file-change-notifier.ts +4 -1
- package/src/modules/kb-fs/git-guarded-filesystem.ts +123 -0
- package/src/modules/kb-fs/locking-filesystem.ts +399 -109
- package/src/modules/kb-fs/node-fs.ts +165 -0
- package/src/modules/kb-fs/read-only-filesystem.ts +18 -9
- package/src/modules/kb-fs/repo-path.ts +213 -3
- package/src/modules/kb-sync/kb-sync.routes.ts +5 -2
- package/src/modules/kb-sync/kb-sync.service.ts +8 -5
- package/src/modules/marketplace/__tests__/github-facade.test.ts +134 -2
- package/src/modules/marketplace/__tests__/marketplace-git.test.ts +2 -1
- package/src/modules/marketplace/__tests__/marketplace-repo.head.test.ts +47 -0
- package/src/modules/marketplace/git-http.routes.ts +7 -4
- package/src/modules/marketplace/github-facade/github-facade-admin.routes.ts +35 -3
- package/src/modules/marketplace/github-facade/github-facade-credentials.service.ts +55 -0
- package/src/modules/marketplace/github-facade/github-facade.routes.ts +32 -6
- package/src/modules/marketplace/github-facade/github-facade.service.ts +80 -19
- package/src/modules/marketplace/github-facade/index.ts +1 -0
- package/src/modules/marketplace/marketplace-repo.service.ts +23 -8
- package/src/modules/mcp/__tests__/downstream-pool.test.ts +278 -0
- package/src/modules/mcp/__tests__/downstream-token-refresh.test.ts +178 -0
- package/src/modules/mcp/__tests__/fake-downstream-mcp-server.ts +156 -0
- package/src/modules/mcp/__tests__/first-call-probe.cli-args.test.ts +178 -0
- package/src/modules/mcp/__tests__/first-call-probe.test.ts +414 -0
- package/src/modules/mcp/__tests__/manual-failure-memo.test.ts +98 -6
- package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +8 -5
- package/src/modules/mcp/__tests__/mcp-routes-harness.ts +1 -3
- package/src/modules/mcp/__tests__/mcp.e2e.test.ts +1055 -59
- package/src/modules/mcp/__tests__/mcp.routes.stateless.test.ts +182 -0
- package/src/modules/mcp/__tests__/mcp.service.test.ts +274 -6
- package/src/modules/mcp/__tests__/surface-log-throttle.test.ts +169 -0
- package/src/modules/mcp/downstream-pool.ts +256 -0
- package/src/modules/mcp/downstream-token-refresh.ts +170 -0
- package/src/modules/mcp/first-call-probe.cli-args.ts +159 -0
- package/src/modules/mcp/first-call-probe.cli.ts +60 -0
- package/src/modules/mcp/first-call-probe.ts +418 -0
- package/src/modules/mcp/manual-failure-memo.ts +81 -23
- package/src/modules/mcp/mcp-auth.middleware.ts +25 -16
- package/src/modules/mcp/mcp.routes.ts +376 -482
- package/src/modules/mcp/mcp.service.ts +846 -176
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +5 -2
- package/src/modules/mcp/oauth/oauth-consent.routes.ts +4 -4
- package/src/modules/mcp/surface-log-throttle.ts +120 -0
- package/src/modules/plugins/__tests__/bundle-dialect.e2e.test.ts +10 -7
- package/src/modules/plugins/__tests__/bundle-dialect.test.ts +165 -0
- package/src/modules/plugins/__tests__/compile-marketplace.test.ts +139 -10
- package/src/modules/plugins/__tests__/fake-join-request-store.ts +189 -0
- package/src/modules/plugins/__tests__/join-request-jobs.service.test.ts +608 -0
- package/src/modules/plugins/__tests__/plugin-creation-display-name.test.ts +241 -0
- package/src/modules/plugins/__tests__/plugin-index.service.test.ts +148 -7
- package/src/modules/plugins/__tests__/plugin-links.service.test.ts +10 -13
- package/src/modules/plugins/__tests__/plugin-names.test.ts +77 -0
- package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +129 -19
- package/src/modules/plugins/__tests__/plugin-rename.service.test.ts +91 -29
- package/src/modules/plugins/__tests__/plugins.routes.test.ts +370 -19
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +131 -0
- package/src/modules/plugins/__tests__/teams.routes.test.ts +248 -0
- package/src/modules/plugins/compile/compile-marketplace.ts +143 -46
- package/src/modules/plugins/compile/marketplace-compiler.service.ts +8 -7
- package/src/modules/plugins/discovery/__tests__/kb-plugin-source.test.ts +56 -20
- package/src/modules/plugins/discovery/bundle-dialect/bundle.source.ts +30 -9
- package/src/modules/plugins/discovery/bundle-dialect/registry.ts +93 -18
- package/src/modules/plugins/discovery/kb-plugin-source.ts +10 -7
- package/src/modules/plugins/discovery/native.source.ts +21 -3
- package/src/modules/plugins/discovery/plugin-source.ts +17 -3
- package/src/modules/plugins/index.ts +16 -3
- package/src/modules/plugins/join-request-jobs.service.ts +675 -0
- package/src/modules/plugins/join-request-records.store.ts +353 -0
- package/src/modules/plugins/join-requests.service.ts +7 -4
- package/src/modules/plugins/plugin-links.service.ts +4 -17
- package/src/modules/plugins/plugin-links.ts +6 -3
- package/src/modules/plugins/plugin-provision.service.ts +209 -37
- package/src/modules/plugins/plugin-rename.service.ts +21 -9
- package/src/modules/plugins/plugins.contract.ts +52 -4
- package/src/modules/plugins/plugins.routes.ts +204 -124
- package/src/modules/plugins/plugins.service.ts +92 -18
- package/src/modules/plugins/plugins.tools.ts +88 -0
- package/src/modules/plugins/teams.routes.ts +129 -0
- package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +359 -15
- package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +248 -0
- package/src/modules/secrets-vault/__tests__/namespace-secrets.test.ts +202 -0
- package/src/modules/secrets-vault/db-secrets-vault.service.ts +217 -35
- package/src/modules/secrets-vault/index.ts +1 -0
- package/src/modules/secrets-vault/mcp-oauth-discovery.service.ts +6 -3
- package/src/modules/secrets-vault/secrets-variable-loader.ts +4 -4
- package/src/modules/secrets-vault/secrets-vault.contract.ts +93 -0
- package/src/modules/secrets-vault/secrets-vault.routes.ts +99 -38
- package/src/modules/settings/__tests__/connection-check.test.ts +233 -0
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +188 -6
- package/src/modules/settings/__tests__/git-root-folders.test.ts +247 -0
- package/src/modules/settings/__tests__/oidc-check.test.ts +185 -0
- package/src/modules/settings/__tests__/setup.routes.boot-failure.test.ts +119 -0
- package/src/modules/settings/__tests__/setup.routes.layout-phase.test.ts +170 -0
- package/src/modules/settings/__tests__/setup.routes.oidc.test.ts +555 -0
- package/src/modules/settings/__tests__/setup.routes.test.ts +667 -16
- package/src/modules/settings/connection-check.ts +351 -0
- package/src/modules/settings/deployment-settings.service.ts +403 -57
- package/src/modules/settings/git-root-folders.ts +171 -0
- package/src/modules/settings/oidc-check.ts +229 -0
- package/src/modules/settings/setup.routes.ts +456 -110
- package/src/modules/skills/__tests__/allowed-tools-check.test.ts +224 -0
- package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +210 -0
- package/src/modules/skills/__tests__/pending-skills.service.test.ts +3 -3
- package/src/modules/skills/__tests__/skills.routes.warnings.test.ts +91 -0
- package/src/modules/skills/__tests__/skills.service.test.ts +223 -2
- package/src/modules/skills/allowed-tools-check.ts +352 -0
- package/src/modules/skills/index.ts +4 -0
- package/src/modules/skills/pending-skills.service.ts +21 -82
- package/src/modules/skills/skill-access-requests.routes.ts +10 -6
- package/src/modules/skills/skills.contract.ts +43 -11
- package/src/modules/skills/skills.routes.ts +16 -1
- package/src/modules/skills/skills.service.ts +214 -84
- package/src/modules/skills/skills.tools.ts +43 -5
- package/src/modules/tool-auth/__tests__/connection-key-rejection.test.ts +192 -0
- package/src/modules/tool-auth/__tests__/connection-keys-admin.routes.test.ts +116 -0
- package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +124 -0
- package/src/modules/tool-auth/connection-key-rejection.ts +25 -0
- package/src/modules/tool-auth/connection-keys-admin.routes.ts +76 -0
- package/src/modules/tool-auth/external-api-key.interface.ts +56 -3
- package/src/modules/tool-auth/external-api-key.service.ts +73 -25
- package/src/modules/tool-auth/internal-token.service.ts +5 -2
- package/src/modules/tool-auth/key-or-session.middleware.ts +45 -0
- package/src/modules/tool-auth/tool-auth.middleware.ts +16 -7
- package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +228 -0
- package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +33 -4
- package/src/modules/tool-helpers/tool-context.ts +46 -7
- package/src/modules/tool-helpers/tool-handler.ts +23 -5
- package/src/modules/tool-helpers/tool.contract.ts +5 -0
- package/src/modules/tool-manuals/__tests__/mcp-server-edit.service.test.ts +2 -1
- package/src/modules/tool-manuals/__tests__/pending-tools.service.test.ts +411 -0
- package/src/modules/tool-manuals/__tests__/tool-delete.route.test.ts +133 -0
- package/src/modules/tool-manuals/__tests__/tool-delete.service.test.ts +469 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +21 -2
- package/src/modules/tool-manuals/__tests__/tool-manuals.cli.test.ts +9 -4
- package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +6 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.invalid.routes.test.ts +254 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.invalid.test.ts +577 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +6 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.pending.route.test.ts +102 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.restart.test.ts +285 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +178 -2
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +114 -2
- package/src/modules/tool-manuals/index.ts +3 -0
- package/src/modules/tool-manuals/mcp-json-discovery.ts +13 -10
- package/src/modules/tool-manuals/mcp-server-edit.service.ts +11 -14
- package/src/modules/tool-manuals/pending-tools.service.ts +179 -0
- package/src/modules/tool-manuals/tool-delete.service.ts +401 -0
- package/src/modules/tool-manuals/tool-manuals.contract.ts +165 -0
- package/src/modules/tool-manuals/tool-manuals.routes.ts +138 -54
- package/src/modules/tool-manuals/tool-manuals.service.ts +432 -87
- package/src/modules/tool-manuals/tool-manuals.tools.ts +81 -6
- package/src/modules/workflow/__tests__/apply-failure.test.ts +1038 -0
- package/src/modules/workflow/__tests__/event-bus.test.ts +61 -9
- package/src/modules/workflow/__tests__/fake-file-lock-db.ts +199 -0
- package/src/modules/workflow/__tests__/file-lock.service.path-identity.test.ts +237 -0
- package/src/modules/workflow/__tests__/format-affected-owners.test.ts +18 -0
- package/src/modules/workflow/__tests__/merge-announces-tree.test.ts +220 -0
- package/src/modules/workflow/__tests__/pending-commits.worker.concurrency.test.ts +187 -0
- package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +2 -0
- package/src/modules/workflow/__tests__/sanitize-error.test.ts +10 -0
- package/src/modules/workflow/__tests__/workflow.routes.folder-proposals.test.ts +128 -0
- package/src/modules/workflow/__tests__/workflow.routes.fork-point-file.test.ts +149 -0
- package/src/modules/workflow/__tests__/workflow.routes.lock-path.test.ts +305 -0
- package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +2 -0
- package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +2 -0
- package/src/modules/workflow/__tests__/workflow.service.deleted-branch-sweep.test.ts +2 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +294 -25
- package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +564 -0
- package/src/modules/workflow/__tests__/workflow.service.platform-restore.test.ts +135 -0
- package/src/modules/workflow/__tests__/workflow.service.read-gate.test.ts +172 -0
- package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +14 -0
- package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +5 -0
- package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +281 -0
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +158 -7
- package/src/modules/workflow/agent-tools/workflow.tools.ts +93 -64
- package/src/modules/workflow/event-bus.ts +26 -28
- package/src/modules/workflow/events.routes.ts +56 -21
- package/src/modules/workflow/file-lock.service.ts +27 -4
- package/src/modules/workflow/git/__tests__/change-request-link.test.ts +43 -0
- package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +1 -0
- package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +238 -1
- package/src/modules/workflow/git/__tests__/git.service.forkPoint.test.ts +245 -0
- package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +35 -0
- package/src/modules/workflow/git/__tests__/git.service.mergeChangeRequest.test.ts +149 -0
- package/src/modules/workflow/git/__tests__/git.service.revertPathsAndPush.test.ts +177 -0
- package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +114 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.getPrDetail.test.ts +69 -2
- package/src/modules/workflow/git/__tests__/pull-request.service.list-fetch.test.ts +121 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.placeholder.test.ts +79 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +1 -1
- package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-cancel.test.ts +1 -1
- package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-update.test.ts +111 -0
- package/src/modules/workflow/git/change-request-link.ts +35 -0
- package/src/modules/workflow/git/git-version.ts +5 -2
- package/src/modules/workflow/git/git.service.ts +684 -144
- package/src/modules/workflow/git/node-git-runner.ts +315 -0
- package/src/modules/workflow/git/pull-request.service.ts +179 -27
- package/src/modules/workflow/pending-commits.service.ts +26 -7
- package/src/modules/workflow/pending-commits.worker.ts +114 -69
- package/src/modules/workflow/recovery-bot.ts +4 -1
- package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +180 -1
- package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +1 -1
- package/src/modules/workflow/review-workflow/__tests__/carry-approvals-forward.test.ts +520 -0
- package/src/modules/workflow/review-workflow/__tests__/erase-approver.test.ts +84 -0
- package/src/modules/workflow/review-workflow/__tests__/merge-gate.test.ts +89 -77
- package/src/modules/workflow/review-workflow/approval-lock.ts +122 -0
- package/src/modules/workflow/review-workflow/review-workflow.interface.ts +59 -7
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +309 -105
- package/src/modules/workflow/sanitize-error.ts +9 -4
- package/src/modules/workflow/workflow.routes.ts +273 -29
- package/src/modules/workflow/workflow.service.ts +1087 -171
- package/src/modules/workspace/__tests__/empty-dirs.test.ts +44 -0
- package/src/modules/workspace/__tests__/file-stat-access.test.ts +301 -0
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +792 -0
- package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +464 -0
- package/src/modules/workspace/__tests__/workspace.routes.branch-errors.test.ts +139 -0
- package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +189 -37
- package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +51 -11
- package/src/modules/workspace/__tests__/workspace.routes.download-implies-read.test.ts +161 -0
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +109 -10
- package/src/modules/workspace/__tests__/workspace.routes.file-writes.test.ts +624 -0
- package/src/modules/workspace/__tests__/workspace.routes.folders.test.ts +409 -0
- package/src/modules/workspace/__tests__/workspace.routes.move-platform-files.test.ts +327 -0
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +25 -6
- package/src/modules/workspace/__tests__/workspace.service.ensure-remotes-fetched.test.ts +109 -0
- package/src/modules/workspace/__tests__/workspace.service.list-cloned.test.ts +6 -5
- package/src/modules/workspace/__tests__/workspace.service.move-never-overwrites.test.ts +218 -0
- package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +56 -1
- package/src/modules/workspace/__tests__/workspace.service.test.ts +811 -42
- package/src/modules/workspace/__tests__/workspace.service.unknown-branch.test.ts +197 -0
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +128 -0
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +212 -0
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +2632 -110
- package/src/modules/workspace/__tests__/write-denial.test.ts +75 -0
- package/src/modules/workspace/empty-dirs.ts +79 -0
- package/src/modules/workspace/file-readers/README.md +32 -0
- package/src/modules/workspace/file-readers/__tests__/doc-extract.test.ts +59 -2
- package/src/modules/workspace/file-readers/__tests__/file-reader.registry.test.ts +133 -4
- package/src/modules/workspace/file-readers/__tests__/fixtures/soft-break.docx +0 -0
- package/src/modules/workspace/file-readers/__tests__/fixtures/tab.docx +0 -0
- package/src/modules/workspace/file-readers/content-mode.ts +87 -0
- package/src/modules/workspace/file-readers/doc-extract.service.ts +4 -1
- package/src/modules/workspace/file-readers/doc-extract.types.ts +8 -4
- package/src/modules/workspace/file-readers/document-reader.ts +6 -0
- package/src/modules/workspace/file-readers/extract-docx.ts +6 -0
- package/src/modules/workspace/file-readers/file-reader.registry.ts +9 -1
- package/src/modules/workspace/file-readers/file-reader.ts +27 -6
- package/src/modules/workspace/file-readers/image-reader.ts +10 -5
- package/src/modules/workspace/file-readers/ooxml-text.ts +38 -2
- package/src/modules/workspace/file-readers/text-reader.ts +61 -4
- package/src/modules/workspace/git-internals.middleware.ts +86 -0
- package/src/modules/workspace/not-found.ts +119 -0
- package/src/modules/workspace/session-ontology.gate.ts +1 -1
- package/src/modules/workspace/startup/__tests__/beside-checkout.test.ts +181 -0
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.degraded.test.ts +361 -0
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +53 -16
- package/src/modules/workspace/startup/beside-checkout.ts +112 -0
- package/src/modules/workspace/startup/kb-git.ts +43 -36
- package/src/modules/workspace/startup/kb-startup-runner.ts +303 -63
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +1215 -73
- package/src/modules/workspace/startup/steps/groups-to-plugins.step.ts +460 -417
- package/src/modules/workspace/startup/steps/personal-spaces.step.ts +191 -0
- package/src/modules/workspace/startup/steps/plugin-display-names.step.ts +169 -0
- package/src/modules/workspace/startup/steps/plugin-layout.ts +110 -0
- package/src/modules/workspace/startup/steps/plugin-manifests.step.ts +46 -85
- package/src/modules/workspace/startup/steps/roles-yaml.step.ts +6 -12
- package/src/modules/workspace/startup/steps/seed-tree.ts +159 -78
- package/src/modules/workspace/startup/steps/template-files.step.ts +492 -141
- package/src/modules/workspace/startup/steps/template-source.ts +168 -0
- package/src/modules/workspace/workspace.routes.ts +687 -257
- package/src/modules/workspace/workspace.service.ts +991 -303
- package/src/modules/workspace/workspace.tools.ts +1787 -180
- package/src/modules/workspace/write-denial.ts +189 -0
- package/src/shared/__tests__/canonical-file-identity.test.ts +154 -0
- package/src/shared/__tests__/email-identity.test.ts +35 -0
- package/src/shared/__tests__/filename.test.ts +0 -0
- package/src/shared/__tests__/frontmatter-id.test.ts +5 -0
- package/src/shared/__tests__/git-failure.test.ts +198 -0
- package/src/shared/__tests__/git-internals.test.ts +115 -0
- package/src/shared/__tests__/http-errors.test.ts +42 -0
- package/src/shared/__tests__/logging.test.ts +185 -0
- package/src/shared/__tests__/path-containment.test.ts +60 -0
- package/src/shared/__tests__/pending-proposals.readAt.test.ts +102 -0
- package/src/shared/__tests__/printable.test.ts +64 -0
- package/src/shared/__tests__/redact-secret.test.ts +122 -0
- package/src/shared/__tests__/rename-no-replace.test.ts +466 -0
- package/src/shared/__tests__/ssrf.test.ts +20 -0
- package/src/shared/canonical-file-identity.ts +92 -0
- package/src/shared/domain-errors.ts +149 -0
- package/src/shared/email-identity.ts +38 -0
- package/src/shared/fs.contract.ts +225 -0
- package/src/shared/git-failure.ts +196 -0
- package/src/shared/git-internals.ts +124 -0
- package/src/shared/git.contract.ts +170 -0
- package/src/shared/http-errors.ts +36 -0
- package/src/shared/logger.contract.ts +141 -0
- package/src/shared/logging.ts +96 -0
- package/src/shared/path-containment.ts +34 -0
- package/src/shared/pending-proposals.ts +161 -0
- package/src/shared/printable.ts +68 -0
- package/src/shared/redact-secret.ts +118 -0
- package/src/shared/rename-no-replace.ts +386 -0
- package/src/shared/ssrf.ts +44 -16
- package/src/version.ts +10 -6
- package/src/modules/diff/diff-paths.ts +0 -9
- package/src/modules/mcp/__tests__/mcp-session-store.test.ts +0 -151
- package/src/modules/mcp/__tests__/mcp.routes.session.test.ts +0 -226
- package/src/modules/mcp/mcp-session-store.ts +0 -162
- package/src/shared/__tests__/fs-walk.test.ts +0 -63
- package/src/shared/__tests__/kb-walk.test.ts +0 -107
- package/src/shared/fs-errors.ts +0 -10
- package/src/shared/fs-walk.ts +0 -45
- package/src/shared/hash-email.ts +0 -14
- package/src/shared/kb-walk.ts +0 -95
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { NodeFs } from '../../kb-fs/node-fs.js';
|
|
2
3
|
import { execFile } from 'node:child_process';
|
|
3
4
|
import { promisify } from 'node:util';
|
|
4
5
|
import fs from 'node:fs/promises';
|
|
@@ -64,7 +65,7 @@ describe('WorkspaceService — branch-keyed identity', () => {
|
|
|
64
65
|
});
|
|
65
66
|
|
|
66
67
|
it('a malformed workspace id (bad percent-escape) rejects as a 400 BranchNameError, not a 500', async () => {
|
|
67
|
-
const svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base');
|
|
68
|
+
const svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base', new NodeFs());
|
|
68
69
|
// `%zz` fails decodeURIComponent, falls back to itself, and `%` never
|
|
69
70
|
// passes the branch-name rules — the cold-path bootstrap must surface
|
|
70
71
|
// that as the status-carrying domain error, not a wrapped plain Error.
|
|
@@ -76,7 +77,7 @@ describe('WorkspaceService — branch-keyed identity', () => {
|
|
|
76
77
|
|
|
77
78
|
it('returns workspace info derived from the branch — no .workspace.json on disk', async () => {
|
|
78
79
|
const { workspaceId, workspaceDir } = await seedBranchWorkspace(root, 'target-company-state');
|
|
79
|
-
const svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base');
|
|
80
|
+
const svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base', new NodeFs());
|
|
80
81
|
const info = await svc.getOrCreateForBranch('target-company-state');
|
|
81
82
|
|
|
82
83
|
expect(info.id).toBe(workspaceId);
|
|
@@ -89,7 +90,7 @@ describe('WorkspaceService — branch-keyed identity', () => {
|
|
|
89
90
|
it('two branches map to distinct directories', async () => {
|
|
90
91
|
const a = await seedBranchWorkspace(root, 'target-company-state');
|
|
91
92
|
const b = await seedBranchWorkspace(root, 'alice/feature');
|
|
92
|
-
const svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base');
|
|
93
|
+
const svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base', new NodeFs());
|
|
93
94
|
|
|
94
95
|
const infoA = await svc.getOrCreateForBranch('target-company-state');
|
|
95
96
|
const infoB = await svc.getOrCreateForBranch('alice/feature');
|
|
@@ -101,22 +102,40 @@ describe('WorkspaceService — branch-keyed identity', () => {
|
|
|
101
102
|
expect(infoB.absolutePath).toBe(path.join(root, 'alice%2Ffeature'));
|
|
102
103
|
});
|
|
103
104
|
|
|
105
|
+
it('refuses a FILE squatting the clone path instead of wiping it, and leaves the bytes alone', async () => {
|
|
106
|
+
// The bootstrap's recovery — wipe and re-clone — is for a crashed
|
|
107
|
+
// bootstrap's directory shell. A regular file at the clone's name is a
|
|
108
|
+
// state a human put the deployment in, and ENOTDIR from the `.git` probe
|
|
109
|
+
// beneath it is absence like any other, so nothing distinguishes the two
|
|
110
|
+
// unless the clone path itself is checked. It must never be deleted.
|
|
111
|
+
const workspaceDir = path.join(root, workspaceIdForBranch('target-company-state'));
|
|
112
|
+
await fs.mkdir(workspaceDir, { recursive: true });
|
|
113
|
+
const squatter = path.join(workspaceDir, 'knowledge-base');
|
|
114
|
+
await fs.writeFile(squatter, 'not a clone', 'utf8');
|
|
115
|
+
|
|
116
|
+
const svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base', new NodeFs());
|
|
117
|
+
await expect(svc.getOrCreateForBranch('target-company-state')).rejects.toThrow(
|
|
118
|
+
/"knowledge-base" in this workspace exists but is not a directory/,
|
|
119
|
+
);
|
|
120
|
+
expect(await fs.readFile(squatter, 'utf8')).toBe('not a clone');
|
|
121
|
+
});
|
|
122
|
+
|
|
104
123
|
it('rejects invalid branch names before touching disk', async () => {
|
|
105
|
-
const svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base');
|
|
124
|
+
const svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base', new NodeFs());
|
|
106
125
|
await expect(svc.getOrCreateForBranch('')).rejects.toThrow();
|
|
107
126
|
await expect(svc.getOrCreateForBranch('-bad-leading-dash')).rejects.toThrow();
|
|
108
127
|
});
|
|
109
128
|
|
|
110
129
|
it('getOrCreateForUser falls back to target-company-state when no branch is supplied', async () => {
|
|
111
130
|
await seedBranchWorkspace(root, 'target-company-state');
|
|
112
|
-
const svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base');
|
|
131
|
+
const svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base', new NodeFs());
|
|
113
132
|
const info = await svc.getOrCreateForUser({ id: 'u', email: 'a@b.c', name: 'A' });
|
|
114
133
|
expect(info.id).toBe(workspaceIdForBranch('target-company-state'));
|
|
115
134
|
});
|
|
116
135
|
|
|
117
136
|
it('getOrCreateForUser respects an explicit branch override', async () => {
|
|
118
137
|
await seedBranchWorkspace(root, 'alice/feature');
|
|
119
|
-
const svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base');
|
|
138
|
+
const svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base', new NodeFs());
|
|
120
139
|
const info = await svc.getOrCreateForUser({ id: 'u', email: 'a@b.c', name: 'A' }, 'alice/feature');
|
|
121
140
|
expect(info.id).toBe(workspaceIdForBranch('alice/feature'));
|
|
122
141
|
});
|
|
@@ -126,14 +145,17 @@ describe('WorkspaceService.createDirectory', () => {
|
|
|
126
145
|
let root: string;
|
|
127
146
|
let svc: WorkspaceService;
|
|
128
147
|
let workspaceDir: string;
|
|
148
|
+
/** Where a created folder goes: inside the checkout, whatever the caller spelled. */
|
|
149
|
+
let repoDir: string;
|
|
129
150
|
let workspaceId: string;
|
|
130
151
|
|
|
131
152
|
beforeEach(async () => {
|
|
132
153
|
root = await mkTmpRoot();
|
|
133
154
|
const seeded = await seedBranchWorkspace(root, 'target-company-state');
|
|
134
155
|
workspaceDir = seeded.workspaceDir;
|
|
156
|
+
repoDir = path.join(workspaceDir, 'knowledge-base');
|
|
135
157
|
workspaceId = seeded.workspaceId;
|
|
136
|
-
svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base');
|
|
158
|
+
svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base', new NodeFs());
|
|
137
159
|
// Hydrate the in-memory map so subsequent ops resolve fast.
|
|
138
160
|
await svc.getWorkspacePath(workspaceId);
|
|
139
161
|
});
|
|
@@ -142,9 +164,11 @@ describe('WorkspaceService.createDirectory', () => {
|
|
|
142
164
|
await fs.rm(root, { recursive: true, force: true });
|
|
143
165
|
});
|
|
144
166
|
|
|
145
|
-
it('creates a .gitkeep inside a fresh empty folder', async () => {
|
|
167
|
+
it('creates a .gitkeep inside a fresh empty folder — in the repository, from an unprefixed path', async () => {
|
|
146
168
|
await svc.createDirectory(workspaceId, 'a');
|
|
147
|
-
const absDir = path.join(
|
|
169
|
+
const absDir = path.join(repoDir, 'a');
|
|
170
|
+
// …and nothing beside the checkout, which is the whole point of the ticket.
|
|
171
|
+
await expect(fs.stat(path.join(workspaceDir, 'a'))).rejects.toMatchObject({ code: 'ENOENT' });
|
|
148
172
|
const dirStat = await fs.stat(absDir);
|
|
149
173
|
expect(dirStat.isDirectory()).toBe(true);
|
|
150
174
|
const gitkeep = await fs.readFile(path.join(absDir, '.gitkeep'), 'utf-8');
|
|
@@ -152,7 +176,7 @@ describe('WorkspaceService.createDirectory', () => {
|
|
|
152
176
|
});
|
|
153
177
|
|
|
154
178
|
it('does not add a .gitkeep when the folder already has content', async () => {
|
|
155
|
-
const absDir = path.join(
|
|
179
|
+
const absDir = path.join(repoDir, 'has-content');
|
|
156
180
|
await fs.mkdir(absDir, { recursive: true });
|
|
157
181
|
await fs.writeFile(path.join(absDir, 'real.md'), 'hello', 'utf-8');
|
|
158
182
|
|
|
@@ -163,7 +187,7 @@ describe('WorkspaceService.createDirectory', () => {
|
|
|
163
187
|
});
|
|
164
188
|
|
|
165
189
|
it('does not duplicate a .gitkeep when one already exists', async () => {
|
|
166
|
-
const absDir = path.join(
|
|
190
|
+
const absDir = path.join(repoDir, 'kept');
|
|
167
191
|
await fs.mkdir(absDir, { recursive: true });
|
|
168
192
|
await fs.writeFile(path.join(absDir, '.gitkeep'), '', 'utf-8');
|
|
169
193
|
|
|
@@ -176,9 +200,9 @@ describe('WorkspaceService.createDirectory', () => {
|
|
|
176
200
|
it('only writes a .gitkeep in the leaf for nested paths', async () => {
|
|
177
201
|
await svc.createDirectory(workspaceId, 'a/b/c');
|
|
178
202
|
|
|
179
|
-
const aEntries = await fs.readdir(path.join(
|
|
180
|
-
const bEntries = await fs.readdir(path.join(
|
|
181
|
-
const cEntries = await fs.readdir(path.join(
|
|
203
|
+
const aEntries = await fs.readdir(path.join(repoDir, 'a'));
|
|
204
|
+
const bEntries = await fs.readdir(path.join(repoDir, 'a', 'b'));
|
|
205
|
+
const cEntries = await fs.readdir(path.join(repoDir, 'a', 'b', 'c'));
|
|
182
206
|
|
|
183
207
|
expect(aEntries).toEqual(['b']);
|
|
184
208
|
expect(bEntries).toEqual(['c']);
|
|
@@ -187,8 +211,8 @@ describe('WorkspaceService.createDirectory', () => {
|
|
|
187
211
|
|
|
188
212
|
it('hides .gitkeep entries from listFiles', async () => {
|
|
189
213
|
await svc.createDirectory(workspaceId, 'visible-empty');
|
|
190
|
-
await fs.writeFile(path.join(
|
|
191
|
-
const mixed = path.join(
|
|
214
|
+
await fs.writeFile(path.join(repoDir, 'visible-empty', '.gitkeep'), '', 'utf-8');
|
|
215
|
+
const mixed = path.join(repoDir, 'mixed');
|
|
192
216
|
await fs.mkdir(mixed, { recursive: true });
|
|
193
217
|
await fs.writeFile(path.join(mixed, '.gitkeep'), '', 'utf-8');
|
|
194
218
|
await fs.writeFile(path.join(mixed, 'real.md'), 'hi', 'utf-8');
|
|
@@ -205,6 +229,418 @@ describe('WorkspaceService.createDirectory', () => {
|
|
|
205
229
|
expect(names).toContain('visible-empty');
|
|
206
230
|
expect(names).toContain('real.md');
|
|
207
231
|
});
|
|
232
|
+
|
|
233
|
+
it('hides mcp-description.md when the root .bevelignore declares the platform rule', async () => {
|
|
234
|
+
const repoDir = path.join(workspaceDir, 'knowledge-base');
|
|
235
|
+
const nestedDir = path.join(repoDir, 'KnowledgeBase');
|
|
236
|
+
await fs.mkdir(nestedDir, { recursive: true });
|
|
237
|
+
await fs.writeFile(path.join(repoDir, '.bevelignore'), '/mcp-description.md\n', 'utf-8');
|
|
238
|
+
await fs.writeFile(path.join(repoDir, 'mcp-description.md'), 'Private deployment preamble', 'utf-8');
|
|
239
|
+
await fs.writeFile(path.join(nestedDir, 'mcp-description.md'), 'Ordinary nested knowledge', 'utf-8');
|
|
240
|
+
await fs.writeFile(path.join(repoDir, 'visible.md'), 'Visible knowledge', 'utf-8');
|
|
241
|
+
|
|
242
|
+
const tree = await svc.listFiles(workspaceId);
|
|
243
|
+
const repo = tree.children?.find((entry) => entry.name === 'knowledge-base');
|
|
244
|
+
const names = repo?.children?.map((entry) => entry.name) ?? [];
|
|
245
|
+
|
|
246
|
+
expect(names).toContain('visible.md');
|
|
247
|
+
expect(repo?.children?.find((entry) => entry.name === 'KnowledgeBase')?.children?.map((entry) => entry.name))
|
|
248
|
+
.toContain('mcp-description.md');
|
|
249
|
+
expect(names).not.toContain('mcp-description.md');
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* `writeFile`'s conditional write: the caller states the bytes it last read
|
|
255
|
+
* and the write is refused if the file no longer holds them. The compare and
|
|
256
|
+
* the write are one call so the route's per-path lock covers both.
|
|
257
|
+
*/
|
|
258
|
+
describe('WorkspaceService.writeFile — expectedContent', () => {
|
|
259
|
+
let root: string;
|
|
260
|
+
let svc: WorkspaceService;
|
|
261
|
+
let workspaceDir: string;
|
|
262
|
+
let workspaceId: string;
|
|
263
|
+
|
|
264
|
+
beforeEach(async () => {
|
|
265
|
+
root = await mkTmpRoot();
|
|
266
|
+
const seeded = await seedBranchWorkspace(root, 'target-company-state');
|
|
267
|
+
workspaceDir = seeded.workspaceDir;
|
|
268
|
+
workspaceId = seeded.workspaceId;
|
|
269
|
+
svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base', new NodeFs());
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
afterEach(async () => {
|
|
273
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('writes when the file still holds the expected content', async () => {
|
|
277
|
+
const rel = 'knowledge-base/mcp-description.md';
|
|
278
|
+
await fs.writeFile(path.join(workspaceDir, rel), 'Before.', 'utf-8');
|
|
279
|
+
|
|
280
|
+
await svc.writeFile(workspaceId, rel, 'After.', { expectedContent: 'Before.' });
|
|
281
|
+
|
|
282
|
+
expect(await fs.readFile(path.join(workspaceDir, rel), 'utf-8')).toBe('After.');
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('refuses with a 409 and leaves the file alone when it changed underneath', async () => {
|
|
286
|
+
const rel = 'knowledge-base/mcp-description.md';
|
|
287
|
+
await fs.writeFile(path.join(workspaceDir, rel), "Another admin's text.", 'utf-8');
|
|
288
|
+
|
|
289
|
+
await expect(
|
|
290
|
+
svc.writeFile(workspaceId, rel, 'My stale merge.', { expectedContent: 'What I loaded.' }),
|
|
291
|
+
).rejects.toMatchObject({ status: 409 });
|
|
292
|
+
|
|
293
|
+
expect(await fs.readFile(path.join(workspaceDir, rel), 'utf-8')).toBe("Another admin's text.");
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it("treats an absent file as the empty string, so expecting '' creates it", async () => {
|
|
297
|
+
const rel = 'knowledge-base/mcp-description.md';
|
|
298
|
+
|
|
299
|
+
await svc.writeFile(workspaceId, rel, 'First write.', { expectedContent: '' });
|
|
300
|
+
|
|
301
|
+
expect(await fs.readFile(path.join(workspaceDir, rel), 'utf-8')).toBe('First write.');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('refuses to create over a file the caller believed absent', async () => {
|
|
305
|
+
const rel = 'knowledge-base/mcp-description.md';
|
|
306
|
+
await fs.writeFile(path.join(workspaceDir, rel), 'Seeded since the editor opened.', 'utf-8');
|
|
307
|
+
|
|
308
|
+
await expect(
|
|
309
|
+
svc.writeFile(workspaceId, rel, 'From an empty editor.', { expectedContent: '' }),
|
|
310
|
+
).rejects.toMatchObject({ status: 409 });
|
|
311
|
+
|
|
312
|
+
expect(await fs.readFile(path.join(workspaceDir, rel), 'utf-8')).toBe('Seeded since the editor opened.');
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('leaves no directory behind when it refuses', async () => {
|
|
316
|
+
// "A refused save leaves nothing behind" has to include the parent chain.
|
|
317
|
+
const rel = 'knowledge-base/new-folder/note.md';
|
|
318
|
+
await fs.writeFile(path.join(workspaceDir, 'knowledge-base', 'taken.md'), 'x', 'utf-8');
|
|
319
|
+
|
|
320
|
+
await expect(
|
|
321
|
+
svc.writeFile(workspaceId, rel, 'mine', { expectedContent: 'not what is there' }),
|
|
322
|
+
).rejects.toMatchObject({ status: 409 });
|
|
323
|
+
|
|
324
|
+
await expect(fs.stat(path.join(workspaceDir, 'knowledge-base', 'new-folder'))).rejects.toMatchObject({
|
|
325
|
+
code: 'ENOENT',
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('still creates the parent chain for a save that goes ahead', async () => {
|
|
330
|
+
const rel = 'knowledge-base/new-folder/note.md';
|
|
331
|
+
|
|
332
|
+
await svc.writeFile(workspaceId, rel, 'mine', { expectedContent: '' });
|
|
333
|
+
|
|
334
|
+
expect(await fs.readFile(path.join(workspaceDir, rel), 'utf-8')).toBe('mine');
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('refuses an absolute path rather than writing it inside the workspace', async () => {
|
|
338
|
+
// The property canonicalisation must not erode: `path.resolve` lets an
|
|
339
|
+
// absolute path win over the workspace dir, and the boundary check is what
|
|
340
|
+
// refuses it. A canonicaliser that dropped the empty leading segment would
|
|
341
|
+
// turn this into an ordinary write at `<workspace>/etc/passwd`.
|
|
342
|
+
//
|
|
343
|
+
// THE NORMALISER is what answers here, and its message names the path and
|
|
344
|
+
// a correction. It refuses the absolute path outright rather than placing
|
|
345
|
+
// it under the repository folder: an unprefixed `etc/passwd` is an ordinary
|
|
346
|
+
// page name, `/etc/passwd` is an attempt to leave, and the two must not be
|
|
347
|
+
// read as the same request.
|
|
348
|
+
await expect(
|
|
349
|
+
svc.writeFile(workspaceId, '/etc/passwd', 'pwned', { expectedContent: '' }),
|
|
350
|
+
).rejects.toThrow('is outside the knowledge base repository');
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('is not applied at all when the option is absent', async () => {
|
|
354
|
+
const rel = 'knowledge-base/mcp-description.md';
|
|
355
|
+
await fs.writeFile(path.join(workspaceDir, rel), 'Whatever.', 'utf-8');
|
|
356
|
+
|
|
357
|
+
await svc.writeFile(workspaceId, rel, 'Replaced.');
|
|
358
|
+
|
|
359
|
+
expect(await fs.readFile(path.join(workspaceDir, rel), 'utf-8')).toBe('Replaced.');
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('serializes writes per path, so two concurrent conditional saves cannot both pass', async () => {
|
|
363
|
+
// The compare and the write are two awaits apart. Without a turn per path
|
|
364
|
+
// both saves read the same bytes, both pass, and the loser's text is gone
|
|
365
|
+
// with no 409 to show for it.
|
|
366
|
+
const rel = 'knowledge-base/mcp-description.md';
|
|
367
|
+
await fs.writeFile(path.join(workspaceDir, rel), 'Before.', 'utf-8');
|
|
368
|
+
|
|
369
|
+
const [first, second] = await Promise.allSettled([
|
|
370
|
+
svc.writeFile(workspaceId, rel, 'From A.', { expectedContent: 'Before.' }),
|
|
371
|
+
svc.writeFile(workspaceId, rel, 'From B.', { expectedContent: 'Before.' }),
|
|
372
|
+
]);
|
|
373
|
+
|
|
374
|
+
const settled = [first, second];
|
|
375
|
+
expect(settled.filter((r) => r.status === 'fulfilled')).toHaveLength(1);
|
|
376
|
+
const rejected = settled.find((r) => r.status === 'rejected') as PromiseRejectedResult;
|
|
377
|
+
expect(rejected.reason).toMatchObject({ status: 409 });
|
|
378
|
+
// Whichever won, the file holds ITS text whole — never a mix, never the loser's.
|
|
379
|
+
const landed = await fs.readFile(path.join(workspaceDir, rel), 'utf-8');
|
|
380
|
+
expect(landed).toBe(first.status === 'fulfilled' ? 'From A.' : 'From B.');
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it('refuses a conditional write against a directory as a client error, not a raw failure', async () => {
|
|
384
|
+
// `GET /file` counts EISDIR as "no file at this path" and the editor opens
|
|
385
|
+
// empty on that 404, so the save that follows must say what is wrong.
|
|
386
|
+
await fs.mkdir(path.join(workspaceDir, 'knowledge-base', 'a-folder'), { recursive: true });
|
|
387
|
+
|
|
388
|
+
await expect(
|
|
389
|
+
svc.writeFile(workspaceId, 'knowledge-base/a-folder', 'text', { expectedContent: '' }),
|
|
390
|
+
).rejects.toMatchObject({ status: 400, message: expect.stringContaining('is a directory') });
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* The per-path turn the conditional write rests on: what queues behind what,
|
|
396
|
+
* that two spellings of one file are one queue, and that a caller holding a
|
|
397
|
+
* turn can still call the ordinary write inside it.
|
|
398
|
+
*/
|
|
399
|
+
describe('WorkspaceService.withPathTurn', () => {
|
|
400
|
+
let root: string;
|
|
401
|
+
let svc: WorkspaceService;
|
|
402
|
+
let workspaceDir: string;
|
|
403
|
+
let workspaceId: string;
|
|
404
|
+
|
|
405
|
+
beforeEach(async () => {
|
|
406
|
+
root = await mkTmpRoot();
|
|
407
|
+
const seeded = await seedBranchWorkspace(root, 'target-company-state');
|
|
408
|
+
workspaceDir = seeded.workspaceDir;
|
|
409
|
+
workspaceId = seeded.workspaceId;
|
|
410
|
+
svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base', new NodeFs());
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
afterEach(async () => {
|
|
414
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
/** Yield to the microtask queue enough times that an unserialized body interleaves. */
|
|
418
|
+
const yieldTwice = async (): Promise<void> => {
|
|
419
|
+
await Promise.resolve();
|
|
420
|
+
await Promise.resolve();
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
it('runs one turn at a time, so two bodies never interleave', async () => {
|
|
424
|
+
const order: string[] = [];
|
|
425
|
+
const body = (name: string) => async (): Promise<void> => {
|
|
426
|
+
order.push(`${name}:enter`);
|
|
427
|
+
await yieldTwice();
|
|
428
|
+
order.push(`${name}:exit`);
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
// Warm the workspace lookup first, for the reason the next test gives:
|
|
432
|
+
// a COLD lookup resolves its callers in disk order rather than call
|
|
433
|
+
// order, so `b` can reach the queue first and this assertion — which
|
|
434
|
+
// names `a` as the one that goes first — fails for a scheduling reason
|
|
435
|
+
// rather than an interleaving one.
|
|
436
|
+
await svc.withPathTurn(workspaceId, 'knowledge-base/mcp-description.md', async () => undefined);
|
|
437
|
+
|
|
438
|
+
await Promise.all([
|
|
439
|
+
svc.withPathTurn(workspaceId, 'knowledge-base/mcp-description.md', body('a')),
|
|
440
|
+
svc.withPathTurn(workspaceId, 'knowledge-base/mcp-description.md', body('b')),
|
|
441
|
+
]);
|
|
442
|
+
|
|
443
|
+
expect(order).toEqual(['a:enter', 'a:exit', 'b:enter', 'b:exit']);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it('treats two spellings of one file as one queue', async () => {
|
|
447
|
+
const order: string[] = [];
|
|
448
|
+
const body = (name: string) => async (): Promise<void> => {
|
|
449
|
+
order.push(`${name}:enter`);
|
|
450
|
+
await yieldTwice();
|
|
451
|
+
order.push(`${name}:exit`);
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
// Warm the workspace lookup first: a turn chains onto the queue only after
|
|
455
|
+
// it, and a COLD lookup resolves its three callers in disk order rather
|
|
456
|
+
// than call order — the queue is one either way, but this asserts FIFO.
|
|
457
|
+
await svc.withPathTurn(workspaceId, 'knowledge-base/mcp-description.md', async () => undefined);
|
|
458
|
+
|
|
459
|
+
await Promise.all([
|
|
460
|
+
svc.withPathTurn(workspaceId, 'knowledge-base/mcp-description.md', body('plain')),
|
|
461
|
+
svc.withPathTurn(workspaceId, './knowledge-base/mcp-description.md', body('dotted')),
|
|
462
|
+
svc.withPathTurn(workspaceId, 'knowledge-base//mcp-description.md', body('doubled')),
|
|
463
|
+
]);
|
|
464
|
+
|
|
465
|
+
expect(order).toEqual([
|
|
466
|
+
'plain:enter', 'plain:exit', 'dotted:enter', 'dotted:exit', 'doubled:enter', 'doubled:exit',
|
|
467
|
+
]);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it('lets another file run while one path is held: a turn is per file', async () => {
|
|
471
|
+
let release: () => void = () => {};
|
|
472
|
+
const held = new Promise<void>((resolve) => {
|
|
473
|
+
release = resolve;
|
|
474
|
+
});
|
|
475
|
+
const holder = svc.withPathTurn(workspaceId, 'knowledge-base/one.md', () => held);
|
|
476
|
+
|
|
477
|
+
// Would hang if one file's turn queued another's.
|
|
478
|
+
await expect(
|
|
479
|
+
svc.withPathTurn(workspaceId, 'knowledge-base/two.md', async () => 'ran'),
|
|
480
|
+
).resolves.toBe('ran');
|
|
481
|
+
|
|
482
|
+
release();
|
|
483
|
+
await holder;
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it('is re-entrant: the write inside a held turn does not wait for itself', async () => {
|
|
487
|
+
const rel = 'knowledge-base/mcp-description.md';
|
|
488
|
+
await fs.writeFile(path.join(workspaceDir, rel), 'Before.', 'utf-8');
|
|
489
|
+
|
|
490
|
+
await svc.withPathTurn(workspaceId, rel, async () => {
|
|
491
|
+
// The same compare-and-write the route makes inside its own turn.
|
|
492
|
+
await svc.writeFile(workspaceId, rel, 'After.', { expectedContent: 'Before.' });
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
expect(await fs.readFile(path.join(workspaceDir, rel), 'utf-8')).toBe('After.');
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it('serializes two writes launched side by side INSIDE a held turn: one lands, the other is refused', async () => {
|
|
499
|
+
// Both inherit the held turn, so neither waits for it — but they must
|
|
500
|
+
// still take turns with each other, or both compare the same old
|
|
501
|
+
// content and the second silently overwrites the first.
|
|
502
|
+
const rel = 'knowledge-base/mcp-description.md';
|
|
503
|
+
const results = await svc.withPathTurn(workspaceId, rel, () =>
|
|
504
|
+
Promise.allSettled([
|
|
505
|
+
svc.writeFile(workspaceId, rel, 'From A.', { expectedContent: '' }),
|
|
506
|
+
svc.writeFile(workspaceId, rel, 'From B.', { expectedContent: '' }),
|
|
507
|
+
]),
|
|
508
|
+
);
|
|
509
|
+
expect(results.filter((r) => r.status === 'fulfilled')).toHaveLength(1);
|
|
510
|
+
const refused = results.find((r) => r.status === 'rejected') as PromiseRejectedResult;
|
|
511
|
+
expect(refused.reason).toMatchObject({ status: 409 });
|
|
512
|
+
expect(await fs.readFile(path.join(workspaceDir, rel), 'utf-8')).toBe('From A.');
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it('updates the diff baseline inside the turn, in the order the mutations landed', async () => {
|
|
516
|
+
const rel = 'knowledge-base/notes.md';
|
|
517
|
+
await fs.writeFile(path.join(workspaceDir, rel), 'Old.', 'utf-8');
|
|
518
|
+
const absolute = path.join(workspaceDir, rel);
|
|
519
|
+
const onDisk = async () => fs.readFile(absolute, 'utf-8').catch(() => null);
|
|
520
|
+
// Each baseline update records what the file held when it ran. The
|
|
521
|
+
// property under test is that an update runs INSIDE its mutation's turn —
|
|
522
|
+
// after that mutation's disk effect and before the next mutation's — so
|
|
523
|
+
// the sequence of updates is the sequence of disk states.
|
|
524
|
+
const calls: Array<{ call: string; disk: string | null }> = [];
|
|
525
|
+
svc.setDiffService({
|
|
526
|
+
markUserDeleted: async () => {
|
|
527
|
+
calls.push({ call: 'deleted', disk: await onDisk() });
|
|
528
|
+
},
|
|
529
|
+
syncFromDisk: async () => {
|
|
530
|
+
calls.push({ call: 'synced', disk: await onDisk() });
|
|
531
|
+
},
|
|
532
|
+
} as never);
|
|
533
|
+
// A delete and a write launched together. Which takes the turn first is
|
|
534
|
+
// not promised (each resolves the workspace before acquiring), so the
|
|
535
|
+
// assertions hold for either order: every update saw its own mutation's
|
|
536
|
+
// effect, and the last update matches what is left on disk.
|
|
537
|
+
await Promise.all([svc.deleteFile(workspaceId, rel), svc.writeFile(workspaceId, rel, 'New.')]);
|
|
538
|
+
expect(calls.map((c) => c.call).sort()).toEqual(['deleted', 'synced']);
|
|
539
|
+
expect(calls.find((c) => c.call === 'deleted')?.disk).toBeNull();
|
|
540
|
+
expect(calls.find((c) => c.call === 'synced')?.disk).toBe('New.');
|
|
541
|
+
expect(await onDisk()).toBe(calls[1].disk);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
it('a throwing turn does not wedge the ones queued behind it', async () => {
|
|
545
|
+
const rel = 'knowledge-base/mcp-description.md';
|
|
546
|
+
|
|
547
|
+
const failed = svc.withPathTurn(workspaceId, rel, async () => {
|
|
548
|
+
throw new Error('boom');
|
|
549
|
+
});
|
|
550
|
+
const after = svc.withPathTurn(workspaceId, rel, async () => 'ran');
|
|
551
|
+
|
|
552
|
+
await expect(failed).rejects.toThrow('boom');
|
|
553
|
+
await expect(after).resolves.toBe('ran');
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
it('holds a delete and an upload of the same path until the turn ends', async () => {
|
|
557
|
+
// The mutators cubic named: neither goes through writeFile, and either
|
|
558
|
+
// landing inside a conditional write's compare-then-write would defeat it.
|
|
559
|
+
const rel = 'knowledge-base/mcp-description.md';
|
|
560
|
+
const absolute = path.join(workspaceDir, rel);
|
|
561
|
+
await fs.writeFile(absolute, 'Held.', 'utf-8');
|
|
562
|
+
let release: () => void = () => {};
|
|
563
|
+
const held = new Promise<void>((resolve) => {
|
|
564
|
+
release = resolve;
|
|
565
|
+
});
|
|
566
|
+
// The turn is taken only after `withPathTurn` has resolved the workspace
|
|
567
|
+
// directory, and so are the contenders' — so calling first does not mean
|
|
568
|
+
// acquiring first. What the service promises is that whoever holds the
|
|
569
|
+
// turn holds it alone; the test must therefore wait until the holder
|
|
570
|
+
// actually HOLDS it before launching the delete and the upload, or one of
|
|
571
|
+
// them can win the acquisition and land first (a real 1-in-5 flake).
|
|
572
|
+
let holding: () => void = () => {};
|
|
573
|
+
const acquired = new Promise<void>((resolve) => {
|
|
574
|
+
holding = resolve;
|
|
575
|
+
});
|
|
576
|
+
const holder = svc.withPathTurn(workspaceId, rel, () => {
|
|
577
|
+
holding();
|
|
578
|
+
return held;
|
|
579
|
+
});
|
|
580
|
+
await acquired;
|
|
581
|
+
|
|
582
|
+
let deleted = false;
|
|
583
|
+
let uploaded = false;
|
|
584
|
+
const del = svc.deleteFile(workspaceId, rel).then(() => {
|
|
585
|
+
deleted = true;
|
|
586
|
+
});
|
|
587
|
+
const up = svc.writeFileBinary(workspaceId, rel, new Uint8Array([1, 2, 3])).then(() => {
|
|
588
|
+
uploaded = true;
|
|
589
|
+
});
|
|
590
|
+
// Real filesystem round trips, not setImmediate: an fs completion runs in
|
|
591
|
+
// the poll phase, which a check-phase callback can jump ahead of, so
|
|
592
|
+
// yielding by microtask would prove nothing about a free `fs.rm`.
|
|
593
|
+
for (let i = 0; i < 5; i += 1) await fs.stat(workspaceDir);
|
|
594
|
+
|
|
595
|
+
expect({ deleted, uploaded }).toEqual({ deleted: false, uploaded: false });
|
|
596
|
+
expect(await fs.readFile(absolute, 'utf-8')).toBe('Held.');
|
|
597
|
+
|
|
598
|
+
release();
|
|
599
|
+
await Promise.all([holder, del, up]);
|
|
600
|
+
expect({ deleted, uploaded }).toEqual({ deleted: true, uploaded: true });
|
|
601
|
+
});
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
describe('WorkspaceService.assertContentMatches', () => {
|
|
605
|
+
let root: string;
|
|
606
|
+
let svc: WorkspaceService;
|
|
607
|
+
let workspaceDir: string;
|
|
608
|
+
let workspaceId: string;
|
|
609
|
+
|
|
610
|
+
beforeEach(async () => {
|
|
611
|
+
root = await mkTmpRoot();
|
|
612
|
+
const seeded = await seedBranchWorkspace(root, 'target-company-state');
|
|
613
|
+
workspaceDir = seeded.workspaceDir;
|
|
614
|
+
workspaceId = seeded.workspaceId;
|
|
615
|
+
svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base', new NodeFs());
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
afterEach(async () => {
|
|
619
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
it('passes silently when the file still holds the expected content', async () => {
|
|
623
|
+
const rel = 'knowledge-base/mcp-description.md';
|
|
624
|
+
await fs.writeFile(path.join(workspaceDir, rel), 'Before.', 'utf-8');
|
|
625
|
+
|
|
626
|
+
await expect(svc.assertContentMatches(workspaceId, rel, 'Before.')).resolves.toBeUndefined();
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
it('refuses a stale precondition with the write path\'s own 409, and writes nothing', async () => {
|
|
630
|
+
const rel = 'knowledge-base/mcp-description.md';
|
|
631
|
+
await fs.writeFile(path.join(workspaceDir, rel), "Another admin's text.", 'utf-8');
|
|
632
|
+
|
|
633
|
+
await expect(svc.assertContentMatches(workspaceId, rel, 'What I loaded.')).rejects.toMatchObject({
|
|
634
|
+
status: 409,
|
|
635
|
+
});
|
|
636
|
+
expect(await fs.readFile(path.join(workspaceDir, rel), 'utf-8')).toBe("Another admin's text.");
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
it('reads an absent file as the empty string', async () => {
|
|
640
|
+
await expect(
|
|
641
|
+
svc.assertContentMatches(workspaceId, 'knowledge-base/mcp-description.md', ''),
|
|
642
|
+
).resolves.toBeUndefined();
|
|
643
|
+
});
|
|
208
644
|
});
|
|
209
645
|
|
|
210
646
|
describe('WorkspaceService.createFolderZip', () => {
|
|
@@ -212,13 +648,16 @@ describe('WorkspaceService.createFolderZip', () => {
|
|
|
212
648
|
let svc: WorkspaceService;
|
|
213
649
|
let workspaceDir: string;
|
|
214
650
|
let workspaceId: string;
|
|
651
|
+
/** The folder a zip request names resolves inside the checkout. */
|
|
652
|
+
let repoDir: string;
|
|
215
653
|
|
|
216
654
|
beforeEach(async () => {
|
|
217
655
|
root = await mkTmpRoot();
|
|
218
656
|
const seeded = await seedBranchWorkspace(root, 'target-company-state');
|
|
219
657
|
workspaceDir = seeded.workspaceDir;
|
|
658
|
+
repoDir = path.join(workspaceDir, 'knowledge-base');
|
|
220
659
|
workspaceId = seeded.workspaceId;
|
|
221
|
-
svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base');
|
|
660
|
+
svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base', new NodeFs());
|
|
222
661
|
await svc.getWorkspacePath(workspaceId);
|
|
223
662
|
});
|
|
224
663
|
|
|
@@ -236,7 +675,7 @@ describe('WorkspaceService.createFolderZip', () => {
|
|
|
236
675
|
}
|
|
237
676
|
|
|
238
677
|
it('zips a folder prefixing entries with the folder name', async () => {
|
|
239
|
-
const dir = path.join(
|
|
678
|
+
const dir = path.join(repoDir, 'docs');
|
|
240
679
|
await fs.mkdir(dir, { recursive: true });
|
|
241
680
|
await fs.writeFile(path.join(dir, 'a.md'), 'alpha');
|
|
242
681
|
await fs.writeFile(path.join(dir, 'b.md'), 'beta');
|
|
@@ -250,7 +689,7 @@ describe('WorkspaceService.createFolderZip', () => {
|
|
|
250
689
|
});
|
|
251
690
|
|
|
252
691
|
it('preserves nested directory structure', async () => {
|
|
253
|
-
const dir = path.join(
|
|
692
|
+
const dir = path.join(repoDir, 'tree');
|
|
254
693
|
await fs.mkdir(path.join(dir, 'nested', 'deep'), { recursive: true });
|
|
255
694
|
await fs.writeFile(path.join(dir, 'top.md'), 'top');
|
|
256
695
|
await fs.writeFile(path.join(dir, 'nested', 'mid.md'), 'mid');
|
|
@@ -266,7 +705,7 @@ describe('WorkspaceService.createFolderZip', () => {
|
|
|
266
705
|
});
|
|
267
706
|
|
|
268
707
|
it('omits .git directories and .gitkeep files', async () => {
|
|
269
|
-
const dir = path.join(
|
|
708
|
+
const dir = path.join(repoDir, 'mixed');
|
|
270
709
|
await fs.mkdir(path.join(dir, '.git', 'objects'), { recursive: true });
|
|
271
710
|
await fs.writeFile(path.join(dir, '.git', 'config'), 'should-not-ship');
|
|
272
711
|
await fs.writeFile(path.join(dir, '.git', 'objects', 'pack'), 'binary');
|
|
@@ -279,7 +718,7 @@ describe('WorkspaceService.createFolderZip', () => {
|
|
|
279
718
|
});
|
|
280
719
|
|
|
281
720
|
it('honors .bevelignore rules from inside the folder', async () => {
|
|
282
|
-
const dir = path.join(
|
|
721
|
+
const dir = path.join(repoDir, 'with-ignore');
|
|
283
722
|
await fs.mkdir(dir, { recursive: true });
|
|
284
723
|
await fs.writeFile(path.join(dir, '.bevelignore'), 'secret.md\n');
|
|
285
724
|
await fs.writeFile(path.join(dir, 'public.md'), 'pub');
|
|
@@ -292,12 +731,19 @@ describe('WorkspaceService.createFolderZip', () => {
|
|
|
292
731
|
});
|
|
293
732
|
|
|
294
733
|
it('refuses to zip a file (not a directory)', async () => {
|
|
295
|
-
await fs.writeFile(path.join(
|
|
734
|
+
await fs.writeFile(path.join(repoDir, 'lone.md'), 'one');
|
|
296
735
|
await expect(svc.createFolderZip(workspaceId, 'lone.md')).rejects.toThrow('Not a directory');
|
|
297
736
|
});
|
|
298
737
|
|
|
299
|
-
it('
|
|
300
|
-
await expect(svc.createFolderZip(workspaceId, '../escape')).rejects.toThrow(
|
|
738
|
+
it('refuses a traversing path, and reads an unprefixed one as the repository folder', async () => {
|
|
739
|
+
await expect(svc.createFolderZip(workspaceId, '../escape')).rejects.toThrow(
|
|
740
|
+
'is outside the knowledge base repository',
|
|
741
|
+
);
|
|
742
|
+
// The unprefixed spelling is the repository's own folder, so it zips.
|
|
743
|
+
await fs.mkdir(path.join(repoDir, 'unprefixed'), { recursive: true });
|
|
744
|
+
await fs.writeFile(path.join(repoDir, 'unprefixed', 'a.md'), 'a');
|
|
745
|
+
const buf = await svc.createFolderZip(workspaceId, 'unprefixed');
|
|
746
|
+
expect((await unzipEntries(buf)).map((e) => e.name)).toEqual(['unprefixed/a.md']);
|
|
301
747
|
});
|
|
302
748
|
|
|
303
749
|
it('throws FolderTooLargeError when contents exceed the size cap', async () => {
|
|
@@ -310,7 +756,7 @@ describe('WorkspaceService.createFolderZip', () => {
|
|
|
310
756
|
// in diff.service.seed-atomicity.test.ts. A re-imported namespace via
|
|
311
757
|
// `await import('node:fs/promises')` is sealed and `vi.spyOn` can't
|
|
312
758
|
// redefine its properties.
|
|
313
|
-
const dir = path.join(
|
|
759
|
+
const dir = path.join(repoDir, 'too-big');
|
|
314
760
|
await fs.mkdir(dir, { recursive: true });
|
|
315
761
|
const filePath = path.join(dir, 'huge.bin');
|
|
316
762
|
await fs.writeFile(filePath, 'x'); // 1 byte real; stat will lie below.
|
|
@@ -367,7 +813,7 @@ describe('WorkspaceService — clone bootstrap & sibling reference', () => {
|
|
|
367
813
|
});
|
|
368
814
|
|
|
369
815
|
it('clones a branch on first bootstrap and checks out the right ref', async () => {
|
|
370
|
-
const svc = new WorkspaceService(workspacesRoot, upstream, 'knowledge-base');
|
|
816
|
+
const svc = new WorkspaceService(workspacesRoot, upstream, 'knowledge-base', new NodeFs());
|
|
371
817
|
const info = await svc.getOrCreateForBranch('target-company-state');
|
|
372
818
|
|
|
373
819
|
const repo = path.join(info.absolutePath, 'knowledge-base');
|
|
@@ -376,7 +822,7 @@ describe('WorkspaceService — clone bootstrap & sibling reference', () => {
|
|
|
376
822
|
});
|
|
377
823
|
|
|
378
824
|
it('uses an existing sibling clone as a --reference and stays dissociated', async () => {
|
|
379
|
-
const svc = new WorkspaceService(workspacesRoot, upstream, 'knowledge-base');
|
|
825
|
+
const svc = new WorkspaceService(workspacesRoot, upstream, 'knowledge-base', new NodeFs());
|
|
380
826
|
// First branch: plain clone — becomes the sibling for the next bootstrap.
|
|
381
827
|
await svc.getOrCreateForBranch('target-company-state');
|
|
382
828
|
// Second branch: should borrow the first clone's objects, then dissociate.
|
|
@@ -404,7 +850,7 @@ describe('WorkspaceService — clone bootstrap & sibling reference', () => {
|
|
|
404
850
|
// post-merge pull of a target branch. Every clone this service hands out must
|
|
405
851
|
// therefore track exactly one upstream ref through exactly one refspec.
|
|
406
852
|
it('stamps a single fetch refspec and upstream ref on a fresh clone', async () => {
|
|
407
|
-
const svc = new WorkspaceService(workspacesRoot, upstream, 'knowledge-base');
|
|
853
|
+
const svc = new WorkspaceService(workspacesRoot, upstream, 'knowledge-base', new NodeFs());
|
|
408
854
|
const info = await svc.getOrCreateForBranch('target-company-state');
|
|
409
855
|
const repo = path.join(info.absolutePath, 'knowledge-base');
|
|
410
856
|
|
|
@@ -428,7 +874,7 @@ describe('WorkspaceService — clone bootstrap & sibling reference', () => {
|
|
|
428
874
|
await runGit(repo, ['config', '--add', 'remote.origin.fetch', '+refs/heads/*:refs/remotes/origin/*']);
|
|
429
875
|
await runGit(repo, ['config', '--add', 'branch.alice/draft.merge', 'refs/heads/target-company-state']);
|
|
430
876
|
|
|
431
|
-
const svc = new WorkspaceService(workspacesRoot, upstream, 'knowledge-base');
|
|
877
|
+
const svc = new WorkspaceService(workspacesRoot, upstream, 'knowledge-base', new NodeFs());
|
|
432
878
|
await svc.getOrCreateForBranch('alice/draft');
|
|
433
879
|
|
|
434
880
|
expect(await gitOut(repo, ['config', '--get-all', 'remote.origin.fetch']))
|
|
@@ -440,7 +886,7 @@ describe('WorkspaceService — clone bootstrap & sibling reference', () => {
|
|
|
440
886
|
});
|
|
441
887
|
|
|
442
888
|
it('notifies the cloned-workspace listener with the workspace id after a clone', async () => {
|
|
443
|
-
const svc = new WorkspaceService(workspacesRoot, upstream, 'knowledge-base');
|
|
889
|
+
const svc = new WorkspaceService(workspacesRoot, upstream, 'knowledge-base', new NodeFs());
|
|
444
890
|
const cloned: string[] = [];
|
|
445
891
|
svc.setWorkspaceClonedListener((id) => cloned.push(id));
|
|
446
892
|
|
|
@@ -449,6 +895,126 @@ describe('WorkspaceService — clone bootstrap & sibling reference', () => {
|
|
|
449
895
|
});
|
|
450
896
|
});
|
|
451
897
|
|
|
898
|
+
/**
|
|
899
|
+
* Reads and writes never follow a symbolic link — as the file itself, or as
|
|
900
|
+
* a directory on the way to it. A link only reaches a repository by direct
|
|
901
|
+
* git push, and following one would hand out (or overwrite) whatever the
|
|
902
|
+
* server process can reach. The workspace root behind a link of the
|
|
903
|
+
* operator's is the one link that is fine.
|
|
904
|
+
*/
|
|
905
|
+
describe('WorkspaceService — symbolic links', () => {
|
|
906
|
+
let root: string;
|
|
907
|
+
let svc: WorkspaceService;
|
|
908
|
+
let workspaceDir: string;
|
|
909
|
+
let workspaceId: string;
|
|
910
|
+
const linkType = process.platform === 'win32' ? 'junction' : 'dir';
|
|
911
|
+
|
|
912
|
+
beforeEach(async () => {
|
|
913
|
+
root = await mkTmpRoot();
|
|
914
|
+
const seeded = await seedBranchWorkspace(root, 'main');
|
|
915
|
+
workspaceDir = seeded.workspaceDir;
|
|
916
|
+
workspaceId = seeded.workspaceId;
|
|
917
|
+
svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base', new NodeFs());
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
afterEach(async () => {
|
|
921
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
it('refuses to read or write a file that is a link, and leaves the target untouched', async () => {
|
|
925
|
+
const secret = path.join(root, 'secret.txt');
|
|
926
|
+
await fs.writeFile(secret, 'DATABASE_URL=postgres://…', 'utf-8');
|
|
927
|
+
const rel = 'knowledge-base/mcp-description.md';
|
|
928
|
+
await fs.symlink(secret, path.join(workspaceDir, rel));
|
|
929
|
+
|
|
930
|
+
await expect(svc.readFile(workspaceId, rel)).rejects.toThrow('Path traversal detected');
|
|
931
|
+
await expect(svc.readFileBinary(workspaceId, rel)).rejects.toThrow('Path traversal detected');
|
|
932
|
+
await expect(svc.writeFile(workspaceId, rel, 'Public text.')).rejects.toThrow('Path traversal detected');
|
|
933
|
+
await expect(svc.writeFileBinary(workspaceId, rel, Buffer.from('x'))).rejects.toThrow('Path traversal detected');
|
|
934
|
+
expect(await fs.readFile(secret, 'utf-8')).toBe('DATABASE_URL=postgres://…');
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
it('refuses a file reached through a linked directory, existing or yet to be created', async () => {
|
|
938
|
+
const elsewhere = path.join(root, 'elsewhere');
|
|
939
|
+
await fs.mkdir(elsewhere, { recursive: true });
|
|
940
|
+
await fs.writeFile(path.join(elsewhere, 'note.md'), 'outside', 'utf-8');
|
|
941
|
+
await fs.symlink(elsewhere, path.join(workspaceDir, 'knowledge-base', 'linked'), linkType);
|
|
942
|
+
|
|
943
|
+
await expect(svc.readFile(workspaceId, 'knowledge-base/linked/note.md')).rejects.toThrow('Path traversal detected');
|
|
944
|
+
await expect(svc.writeFile(workspaceId, 'knowledge-base/linked/new/deeper.md', 'x')).rejects.toThrow(
|
|
945
|
+
'Path traversal detected',
|
|
946
|
+
);
|
|
947
|
+
expect(await fs.readdir(elsewhere)).toEqual(['note.md']);
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
it('refuses a path through a DANGLING link — its absence is not leave to climb past it', async () => {
|
|
951
|
+
// A link to nothing resolves to nothing, like a missing folder would; a
|
|
952
|
+
// guard that then climbed to the parent would pass, and the write would
|
|
953
|
+
// land wherever the link is pointed at by the time it runs.
|
|
954
|
+
// A junction needs an existing target, so on Windows the dangling link is
|
|
955
|
+
// a file link — it may point at nothing, and lstat still reports a link.
|
|
956
|
+
await fs.symlink(
|
|
957
|
+
path.join(root, 'nowhere'),
|
|
958
|
+
path.join(workspaceDir, 'knowledge-base', 'dangling'),
|
|
959
|
+
process.platform === 'win32' ? 'file' : 'dir',
|
|
960
|
+
);
|
|
961
|
+
await expect(svc.writeFile(workspaceId, 'knowledge-base/dangling/note.md', 'x')).rejects.toThrow(
|
|
962
|
+
'Path traversal detected',
|
|
963
|
+
);
|
|
964
|
+
await expect(fs.access(path.join(root, 'nowhere'))).rejects.toThrow();
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
it('refuses a directory, a move and an extraction that would go through a link', async () => {
|
|
968
|
+
const elsewhere = path.join(root, 'elsewhere');
|
|
969
|
+
await fs.mkdir(elsewhere, { recursive: true });
|
|
970
|
+
await fs.symlink(elsewhere, path.join(workspaceDir, 'knowledge-base', 'linked'), linkType);
|
|
971
|
+
await svc.writeFile(workspaceId, 'knowledge-base/real.md', 'real');
|
|
972
|
+
|
|
973
|
+
await expect(svc.createDirectory(workspaceId, 'knowledge-base/linked/new-folder')).rejects.toThrow(
|
|
974
|
+
'Path traversal detected',
|
|
975
|
+
);
|
|
976
|
+
await expect(svc.moveEntry(workspaceId, 'knowledge-base/real.md', 'knowledge-base/linked/real.md')).rejects.toThrow(
|
|
977
|
+
'Path traversal detected',
|
|
978
|
+
);
|
|
979
|
+
expect(await fs.readFile(path.join(workspaceDir, 'knowledge-base', 'real.md'), 'utf-8')).toBe('real');
|
|
980
|
+
|
|
981
|
+
// An archive whose entry lands under the link: that entry is skipped and
|
|
982
|
+
// reported; the rest extracts.
|
|
983
|
+
const { default: AdmZip } = await import('adm-zip');
|
|
984
|
+
const zip = new AdmZip();
|
|
985
|
+
zip.addFile('linked/escaped.md', Buffer.from('out'));
|
|
986
|
+
zip.addFile('kept.md', Buffer.from('in'));
|
|
987
|
+
await fs.writeFile(path.join(workspaceDir, 'knowledge-base', 'a.zip'), zip.toBuffer());
|
|
988
|
+
const res = await svc.unzipFile(workspaceId, 'knowledge-base/a.zip', 'knowledge-base');
|
|
989
|
+
expect(res.extracted).toEqual(['knowledge-base/kept.md']);
|
|
990
|
+
expect(res.skipped).toContainEqual({ path: 'linked/escaped.md', reason: 'Path traversal detected' });
|
|
991
|
+
expect(await fs.readdir(elsewhere)).toEqual([]);
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
it('reads and writes as before when nothing on the path is a link, the workspace root behind one included', async () => {
|
|
995
|
+
const rel = 'knowledge-base/Folder/new/page.md';
|
|
996
|
+
await svc.writeFile(workspaceId, rel, 'Hello.');
|
|
997
|
+
expect(await svc.readFile(workspaceId, rel)).toBe('Hello.');
|
|
998
|
+
|
|
999
|
+
// A mounted-volume shape: the whole workspaces root reached through a link.
|
|
1000
|
+
const mount = path.join(root, 'mount');
|
|
1001
|
+
await fs.symlink(root, mount, linkType);
|
|
1002
|
+
const viaMount = new WorkspaceService(mount, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base', new NodeFs());
|
|
1003
|
+
expect(await viaMount.readFile(workspaceId, rel)).toBe('Hello.');
|
|
1004
|
+
await viaMount.writeFile(workspaceId, rel, 'Hello again.');
|
|
1005
|
+
expect(await fs.readFile(path.join(workspaceDir, rel), 'utf-8')).toBe('Hello again.');
|
|
1006
|
+
|
|
1007
|
+
// One workspace directory mounted elsewhere: the directory itself is a
|
|
1008
|
+
// link of the operator's, and everything beneath it is its own.
|
|
1009
|
+
const elsewhere = path.join(root, 'elsewhere-ws');
|
|
1010
|
+
await fs.rename(workspaceDir, elsewhere);
|
|
1011
|
+
await fs.symlink(elsewhere, workspaceDir, linkType);
|
|
1012
|
+
expect(await svc.readFile(workspaceId, rel)).toBe('Hello again.');
|
|
1013
|
+
await svc.writeFile(workspaceId, rel, 'Third.');
|
|
1014
|
+
expect(await fs.readFile(path.join(elsewhere, rel), 'utf-8')).toBe('Third.');
|
|
1015
|
+
});
|
|
1016
|
+
});
|
|
1017
|
+
|
|
452
1018
|
describe('WorkspaceService.sweepOrphanedWorkspaces', () => {
|
|
453
1019
|
let root: string;
|
|
454
1020
|
|
|
@@ -466,7 +1032,7 @@ describe('WorkspaceService.sweepOrphanedWorkspaces', () => {
|
|
|
466
1032
|
|
|
467
1033
|
it('removes the clone of a branch that is not in the known set', async () => {
|
|
468
1034
|
const { workspaceId, workspaceDir } = await seedBranchWorkspace(root, 'target-company-state');
|
|
469
|
-
const svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base');
|
|
1035
|
+
const svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base', new NodeFs());
|
|
470
1036
|
await svc.getOrCreateForBranch('target-company-state');
|
|
471
1037
|
|
|
472
1038
|
// The branch vanishes from the known set; the sweep reclaims its clone.
|
|
@@ -487,7 +1053,7 @@ describe('WorkspaceService.readAllKbFiles', () => {
|
|
|
487
1053
|
const seeded = await seedBranchWorkspace(root, 'target-company-state');
|
|
488
1054
|
repoRoot = path.join(seeded.workspaceDir, 'knowledge-base');
|
|
489
1055
|
workspaceId = seeded.workspaceId;
|
|
490
|
-
svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base');
|
|
1056
|
+
svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base', new NodeFs());
|
|
491
1057
|
await svc.getWorkspacePath(workspaceId);
|
|
492
1058
|
});
|
|
493
1059
|
|
|
@@ -536,14 +1102,17 @@ describe('WorkspaceService.unzipFile — ontology-session write guard', () => {
|
|
|
536
1102
|
let root: string;
|
|
537
1103
|
let svc: WorkspaceService;
|
|
538
1104
|
let workspaceDir: string;
|
|
1105
|
+
/** Archive and destination alike resolve inside the checkout. */
|
|
1106
|
+
let repoDir: string;
|
|
539
1107
|
let workspaceId: string;
|
|
540
1108
|
|
|
541
1109
|
beforeEach(async () => {
|
|
542
1110
|
root = await mkTmpRoot();
|
|
543
1111
|
const seeded = await seedBranchWorkspace(root, 'target-company-state');
|
|
544
1112
|
workspaceDir = seeded.workspaceDir;
|
|
1113
|
+
repoDir = path.join(workspaceDir, 'knowledge-base');
|
|
545
1114
|
workspaceId = seeded.workspaceId;
|
|
546
|
-
svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base');
|
|
1115
|
+
svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base', new NodeFs());
|
|
547
1116
|
await svc.getWorkspacePath(workspaceId);
|
|
548
1117
|
});
|
|
549
1118
|
|
|
@@ -555,7 +1124,7 @@ describe('WorkspaceService.unzipFile — ontology-session write guard', () => {
|
|
|
555
1124
|
const { default: AdmZip } = await import('adm-zip');
|
|
556
1125
|
const zip = new AdmZip();
|
|
557
1126
|
for (const [name, content] of Object.entries(files)) zip.addFile(name, Buffer.from(content));
|
|
558
|
-
await fs.writeFile(path.join(
|
|
1127
|
+
await fs.writeFile(path.join(repoDir, rel), zip.toBuffer());
|
|
559
1128
|
}
|
|
560
1129
|
|
|
561
1130
|
it('skips entries the write guard rejects and never writes them to disk', async () => {
|
|
@@ -563,10 +1132,13 @@ describe('WorkspaceService.unzipFile — ontology-session write guard', () => {
|
|
|
563
1132
|
const res = await svc.unzipFile(workspaceId, 'a.zip', 'out', async (wsPath) => {
|
|
564
1133
|
if (wsPath.endsWith('blocked.md')) throw new Error('Blocked by the ontology-session boundary');
|
|
565
1134
|
});
|
|
566
|
-
|
|
1135
|
+
// An unprefixed destination is the repository's own `out/`, and the
|
|
1136
|
+
// extracted paths are reported workspace-relative, so prefixed.
|
|
1137
|
+
expect(res.extracted).toEqual(['knowledge-base/out/keep.md']);
|
|
567
1138
|
expect(res.skipped).toContainEqual({ path: 'blocked.md', reason: 'Blocked by the ontology-session boundary' });
|
|
568
|
-
expect((await fs.readFile(path.join(
|
|
569
|
-
await expect(fs.readFile(path.join(
|
|
1139
|
+
expect((await fs.readFile(path.join(repoDir, 'out', 'keep.md'))).toString()).toBe('ok');
|
|
1140
|
+
await expect(fs.readFile(path.join(repoDir, 'out', 'blocked.md'))).rejects.toThrow();
|
|
1141
|
+
await expect(fs.stat(path.join(workspaceDir, 'out'))).rejects.toMatchObject({ code: 'ENOENT' });
|
|
570
1142
|
});
|
|
571
1143
|
|
|
572
1144
|
it('skips a guard-rejected directory entry without creating it on disk', async () => {
|
|
@@ -574,15 +1146,15 @@ describe('WorkspaceService.unzipFile — ontology-session write guard', () => {
|
|
|
574
1146
|
const zip = new AdmZip();
|
|
575
1147
|
zip.addFile('blocked-dir/', Buffer.alloc(0)); // bare directory entry
|
|
576
1148
|
zip.addFile('keep.md', Buffer.from('ok'));
|
|
577
|
-
await fs.writeFile(path.join(
|
|
1149
|
+
await fs.writeFile(path.join(repoDir, 'c.zip'), zip.toBuffer());
|
|
578
1150
|
|
|
579
1151
|
const res = await svc.unzipFile(workspaceId, 'c.zip', 'out', async (wsPath) => {
|
|
580
1152
|
if (wsPath.includes('blocked-dir')) throw new Error('Blocked by the ontology-session boundary');
|
|
581
1153
|
});
|
|
582
1154
|
|
|
583
|
-
expect(res.extracted).toEqual(['out/keep.md']);
|
|
1155
|
+
expect(res.extracted).toEqual(['knowledge-base/out/keep.md']);
|
|
584
1156
|
expect(res.skipped).toContainEqual({ path: 'blocked-dir/', reason: 'Blocked by the ontology-session boundary' });
|
|
585
|
-
await expect(fs.stat(path.join(
|
|
1157
|
+
await expect(fs.stat(path.join(repoDir, 'out', 'blocked-dir'))).rejects.toThrow();
|
|
586
1158
|
});
|
|
587
1159
|
|
|
588
1160
|
it('does not create the destination directory when every entry is blocked', async () => {
|
|
@@ -592,13 +1164,210 @@ describe('WorkspaceService.unzipFile — ontology-session write guard', () => {
|
|
|
592
1164
|
});
|
|
593
1165
|
expect(res.extracted).toEqual([]);
|
|
594
1166
|
expect(res.skipped).toHaveLength(2);
|
|
595
|
-
await expect(fs.stat(path.join(
|
|
1167
|
+
await expect(fs.stat(path.join(repoDir, 'out'))).rejects.toThrow();
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
// The zip reader cannot tell a missing file from a corrupt one: it answers
|
|
1171
|
+
// "ADM-ZIP: Invalid filename" for both, and unzip used to dress that up as a
|
|
1172
|
+
// 422 unreadable archive — blaming bytes that were never there. Absence is
|
|
1173
|
+
// asked first, so the tool can give it the one 404 every file tool gives.
|
|
1174
|
+
it('a .zip that is not there is a 404 not_found, not an unreadable archive', async () => {
|
|
1175
|
+
await expect(svc.unzipFile(workspaceId, 'nope.zip')).rejects.toMatchObject({
|
|
1176
|
+
name: 'PathNotFoundError',
|
|
1177
|
+
status: 404,
|
|
1178
|
+
payload: { kind: 'not_found', path: 'knowledge-base/nope.zip' },
|
|
1179
|
+
});
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
// The error carries the WHOLE answer — sanitized path, kind AND the next
|
|
1183
|
+
// step — because not every surface re-renders it. `POST /workspace/:id/unzip`
|
|
1184
|
+
// hands this straight to `domainErrorBody`, so whatever is missing here is
|
|
1185
|
+
// missing from the HTTP 404 that the tools' 404 is supposed to match.
|
|
1186
|
+
it('the missing-archive 404 carries the next step and a one-line path, on any surface', async () => {
|
|
1187
|
+
await expect(svc.unzipFile(workspaceId, 'nope.zip')).rejects.toMatchObject({
|
|
1188
|
+
message:
|
|
1189
|
+
'There is no file or directory at "knowledge-base/nope.zip" in this workspace. Check the path with list_files.',
|
|
1190
|
+
});
|
|
1191
|
+
// A name carrying a line break cannot forge a second line of the answer,
|
|
1192
|
+
// in the message or in the `path` a JSON consumer reads.
|
|
1193
|
+
const forged = 'a\nb\u2028c.zip';
|
|
1194
|
+
await expect(svc.unzipFile(workspaceId, forged)).rejects.toMatchObject({
|
|
1195
|
+
status: 404,
|
|
1196
|
+
payload: { kind: 'not_found', path: 'knowledge-base/a\\nb\\u2028c.zip' },
|
|
1197
|
+
});
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1200
|
+
// The archive is read ONCE and the reader is handed those bytes. With a
|
|
1201
|
+
// `stat` probe followed by `new AdmZip(path)` there were two moments: an
|
|
1202
|
+
// archive that vanished in between passed the probe and then failed the
|
|
1203
|
+
// open, and the caller got back the 422 the probe existed to prevent.
|
|
1204
|
+
it('an archive that vanishes as it is opened still answers 404, never the 422', async () => {
|
|
1205
|
+
await writeZip('racy.zip', { 'x.md': '1' });
|
|
1206
|
+
const gone = Object.assign(new Error('ENOENT: no such file or directory'), { code: 'ENOENT' });
|
|
1207
|
+
const readSpy = vi.spyOn(fs, 'readFile').mockRejectedValueOnce(gone);
|
|
1208
|
+
try {
|
|
1209
|
+
await expect(svc.unzipFile(workspaceId, 'racy.zip')).rejects.toMatchObject({
|
|
1210
|
+
name: 'PathNotFoundError',
|
|
1211
|
+
status: 404,
|
|
1212
|
+
payload: { kind: 'not_found', path: 'knowledge-base/racy.zip' },
|
|
1213
|
+
});
|
|
1214
|
+
} finally {
|
|
1215
|
+
readSpy.mockRestore();
|
|
1216
|
+
}
|
|
1217
|
+
});
|
|
1218
|
+
|
|
1219
|
+
// The other half of that single read: bytes that ARE there but are not a zip
|
|
1220
|
+
// are still the archive's problem, not the path's.
|
|
1221
|
+
it('a file that is not a zip is still the 422, read from its bytes', async () => {
|
|
1222
|
+
await fs.writeFile(path.join(repoDir, 'junk.zip'), 'not really a zip');
|
|
1223
|
+
await expect(svc.unzipFile(workspaceId, 'junk.zip')).rejects.toMatchObject({
|
|
1224
|
+
status: 422,
|
|
1225
|
+
payload: { kind: 'unreadable-archive' },
|
|
1226
|
+
});
|
|
596
1227
|
});
|
|
597
1228
|
|
|
598
1229
|
it('extracts everything when no guard is supplied (human / non-agent path)', async () => {
|
|
599
1230
|
await writeZip('b.zip', { 'x.md': '1', 'y.md': '2' });
|
|
600
1231
|
const res = await svc.unzipFile(workspaceId, 'b.zip', 'out');
|
|
601
|
-
expect(res.extracted.sort()).toEqual(['out/x.md', 'out/y.md']);
|
|
1232
|
+
expect(res.extracted.sort()).toEqual(['knowledge-base/out/x.md', 'knowledge-base/out/y.md']);
|
|
602
1233
|
expect(res.skipped).toEqual([]);
|
|
603
1234
|
});
|
|
604
1235
|
});
|
|
1236
|
+
|
|
1237
|
+
/**
|
|
1238
|
+
* The service resolves a workspace path in ONE place, and that place does two
|
|
1239
|
+
* things: it places the path inside the repository, and it checks the RESOLVED
|
|
1240
|
+
* location against `<workspaceDir>/<kbDirName>`. Nine inline
|
|
1241
|
+
* `path.resolve(workspaceDir, …)` calls used to do the first half only — which
|
|
1242
|
+
* is how an unprefixed path written by a staging test ended up beside the
|
|
1243
|
+
* checkout, never committed and never pushed.
|
|
1244
|
+
*/
|
|
1245
|
+
describe('WorkspaceService — every operation resolves inside the repository', () => {
|
|
1246
|
+
let root: string;
|
|
1247
|
+
let svc: WorkspaceService;
|
|
1248
|
+
let workspaceDir: string;
|
|
1249
|
+
let repoDir: string;
|
|
1250
|
+
let workspaceId: string;
|
|
1251
|
+
|
|
1252
|
+
beforeEach(async () => {
|
|
1253
|
+
root = await mkTmpRoot();
|
|
1254
|
+
const seeded = await seedBranchWorkspace(root, 'target-company-state');
|
|
1255
|
+
workspaceDir = seeded.workspaceDir;
|
|
1256
|
+
repoDir = path.join(workspaceDir, 'knowledge-base');
|
|
1257
|
+
workspaceId = seeded.workspaceId;
|
|
1258
|
+
svc = new WorkspaceService(root, 'https://example.invalid/kb.git', 'knowledge-base', new NodeFs());
|
|
1259
|
+
await svc.getWorkspacePath(workspaceId);
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
afterEach(async () => {
|
|
1263
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
/** Whatever sits in the workspace directory other than the checkout. */
|
|
1267
|
+
const besideCheckout = async () => (await fs.readdir(workspaceDir)).filter((n) => n !== 'knowledge-base').sort();
|
|
1268
|
+
|
|
1269
|
+
it('puts every write an unprefixed path names inside the checkout', async () => {
|
|
1270
|
+
await svc.writeFile(workspaceId, 'KnowledgeBase/Report.md', 'body');
|
|
1271
|
+
await svc.writeFileBinary(workspaceId, 'Uploads/TestDocx.docx', Buffer.from('bytes'));
|
|
1272
|
+
await svc.createDirectory(workspaceId, 'Plugins/GTM');
|
|
1273
|
+
|
|
1274
|
+
expect(await fs.readFile(path.join(repoDir, 'KnowledgeBase', 'Report.md'), 'utf-8')).toBe('body');
|
|
1275
|
+
expect(await fs.readFile(path.join(repoDir, 'Uploads', 'TestDocx.docx'), 'utf-8')).toBe('bytes');
|
|
1276
|
+
expect((await fs.stat(path.join(repoDir, 'Plugins', 'GTM'))).isDirectory()).toBe(true);
|
|
1277
|
+
expect(await besideCheckout()).toEqual([]);
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1280
|
+
it('reads, moves and deletes the repository file an unprefixed path names', async () => {
|
|
1281
|
+
await svc.writeFile(workspaceId, 'a.md', 'one');
|
|
1282
|
+
|
|
1283
|
+
expect(await svc.readFile(workspaceId, 'a.md')).toBe('one');
|
|
1284
|
+
expect((await svc.readFileBinary(workspaceId, 'a.md')).toString()).toBe('one');
|
|
1285
|
+
// Both spellings are the same file.
|
|
1286
|
+
expect(await svc.readFile(workspaceId, 'knowledge-base/a.md')).toBe('one');
|
|
1287
|
+
|
|
1288
|
+
await svc.moveEntry(workspaceId, 'a.md', 'Moved/a.md');
|
|
1289
|
+
expect(await fs.readFile(path.join(repoDir, 'Moved', 'a.md'), 'utf-8')).toBe('one');
|
|
1290
|
+
|
|
1291
|
+
await svc.deleteFile(workspaceId, 'Moved/a.md');
|
|
1292
|
+
await expect(fs.stat(path.join(repoDir, 'Moved', 'a.md'))).rejects.toMatchObject({ code: 'ENOENT' });
|
|
1293
|
+
expect(await besideCheckout()).toEqual([]);
|
|
1294
|
+
});
|
|
1295
|
+
|
|
1296
|
+
it('takes its path and folder turns on the repository path, so two spellings are one queue', async () => {
|
|
1297
|
+
const order: string[] = [];
|
|
1298
|
+
const slow = svc.withPathTurn(workspaceId, 'contended.md', async () => {
|
|
1299
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
1300
|
+
order.push('first');
|
|
1301
|
+
});
|
|
1302
|
+
const fast = svc.withPathTurn(workspaceId, 'knowledge-base/contended.md', async () => {
|
|
1303
|
+
order.push('second');
|
|
1304
|
+
});
|
|
1305
|
+
await Promise.all([slow, fast]);
|
|
1306
|
+
expect(order).toEqual(['first', 'second']);
|
|
1307
|
+
|
|
1308
|
+
await svc.withFolderTurn(workspaceId, 'Folder', async () => undefined);
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1311
|
+
it('refuses a path that resolves outside the checkout after normalisation', async () => {
|
|
1312
|
+
// The spelling is contained and normalises into the repository, so the
|
|
1313
|
+
// LEXICAL root check in `resolveInsideRepo` passes it — `knowledge-base/
|
|
1314
|
+
// Escape/x.md` stays under the repository however `Escape` resolves. What
|
|
1315
|
+
// refuses it is `assertNotThroughLink`, which follows the link. Both run
|
|
1316
|
+
// after normalisation, and this pins the one that can actually fire on a
|
|
1317
|
+
// spelling the normaliser accepts.
|
|
1318
|
+
const outside = path.join(workspaceDir, 'outside');
|
|
1319
|
+
await fs.mkdir(outside, { recursive: true });
|
|
1320
|
+
await fs.symlink(outside, path.join(repoDir, 'Escape'), process.platform === 'win32' ? 'junction' : 'dir');
|
|
1321
|
+
|
|
1322
|
+
await expect(svc.writeFile(workspaceId, 'Escape/x.md', 'x')).rejects.toThrow('Path traversal detected');
|
|
1323
|
+
await expect(svc.readFile(workspaceId, 'Escape/x.md')).rejects.toThrow('Path traversal detected');
|
|
1324
|
+
expect(await fs.readdir(outside)).toEqual([]);
|
|
1325
|
+
});
|
|
1326
|
+
|
|
1327
|
+
it('cannot be asked for the workspace directory itself, or anything beside the checkout', async () => {
|
|
1328
|
+
// The workspace directory has no spelling left: every path names something
|
|
1329
|
+
// under the checkout, and the ones that used to reach up are refused.
|
|
1330
|
+
for (const dir of ['', '.', '..', '/', 'knowledge-base/..']) {
|
|
1331
|
+
await expect(svc.readFile(workspaceId, dir)).rejects.toThrow(
|
|
1332
|
+
'is outside the knowledge base repository',
|
|
1333
|
+
);
|
|
1334
|
+
}
|
|
1335
|
+
await fs.writeFile(path.join(workspaceDir, 'stray.md'), 'already there', 'utf-8');
|
|
1336
|
+
// `stray.md` names the REPOSITORY's `stray.md` now — the one beside the
|
|
1337
|
+
// checkout has no path that reaches it, which is the point.
|
|
1338
|
+
await expect(svc.readFile(workspaceId, 'stray.md')).rejects.toMatchObject({ code: 'ENOENT' });
|
|
1339
|
+
await svc.writeFile(workspaceId, 'stray.md', 'in the repository');
|
|
1340
|
+
expect(await fs.readFile(path.join(workspaceDir, 'stray.md'), 'utf-8')).toBe('already there');
|
|
1341
|
+
expect(await fs.readFile(path.join(repoDir, 'stray.md'), 'utf-8')).toBe('in the repository');
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1344
|
+
it('reserves the checkout folder name at the repository root on create, move and write', async () => {
|
|
1345
|
+
const reserved = /"knowledge-base" is reserved/;
|
|
1346
|
+
await expect(svc.createDirectory(workspaceId, 'knowledge-base/knowledge-base')).rejects.toThrow(reserved);
|
|
1347
|
+
await expect(svc.writeFile(workspaceId, 'knowledge-base/knowledge-base/x.md', 'x')).rejects.toThrow(reserved);
|
|
1348
|
+
await expect(
|
|
1349
|
+
svc.writeFileBinary(workspaceId, 'knowledge-base/knowledge-base/x.bin', Buffer.from('x')),
|
|
1350
|
+
).rejects.toThrow(reserved);
|
|
1351
|
+
|
|
1352
|
+
await svc.writeFile(workspaceId, 'movable.md', 'm');
|
|
1353
|
+
await expect(svc.moveEntry(workspaceId, 'movable.md', 'knowledge-base/knowledge-base/movable.md')).rejects.toThrow(
|
|
1354
|
+
reserved,
|
|
1355
|
+
);
|
|
1356
|
+
await expect(fs.stat(path.join(repoDir, 'knowledge-base'))).rejects.toMatchObject({ code: 'ENOENT' });
|
|
1357
|
+
|
|
1358
|
+
// The refusal says why, so nobody has to guess.
|
|
1359
|
+
await expect(svc.createDirectory(workspaceId, 'knowledge-base/knowledge-base')).rejects.toThrow(
|
|
1360
|
+
/it is the checkout folder's name/,
|
|
1361
|
+
);
|
|
1362
|
+
// A case variant is the same folder wherever the repository is cloned onto
|
|
1363
|
+
// a case-insensitive filesystem, so it is reserved in every spelling.
|
|
1364
|
+
await expect(svc.createDirectory(workspaceId, 'Knowledge-Base')).rejects.toThrow(/"Knowledge-Base" is reserved/);
|
|
1365
|
+
await expect(svc.writeFile(workspaceId, 'KNOWLEDGE-BASE/x.md', 'x')).rejects.toThrow(
|
|
1366
|
+
/refused in every spelling/,
|
|
1367
|
+
);
|
|
1368
|
+
|
|
1369
|
+
// A namesake deeper in the tree is ordinary content.
|
|
1370
|
+
await svc.createDirectory(workspaceId, 'KnowledgeBase/knowledge-base');
|
|
1371
|
+
expect((await fs.stat(path.join(repoDir, 'KnowledgeBase', 'knowledge-base'))).isDirectory()).toBe(true);
|
|
1372
|
+
});
|
|
1373
|
+
});
|