@bevel-software/platform-core-backend 0.13.6 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD-PARTY-NOTICES.md +6 -6
- package/dist/core/catalog-cache-invalidation.d.ts +29 -0
- package/dist/core/catalog-cache-invalidation.d.ts.map +1 -0
- package/dist/core/catalog-cache-invalidation.js +71 -0
- package/dist/core/catalog-cache-invalidation.js.map +1 -0
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +101 -9
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +20 -1
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +105 -43
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core-config.d.ts.map +1 -1
- package/dist/core-config.js +12 -5
- package/dist/core-config.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +18 -6
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +26 -1
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +213 -71
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +59 -7
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access-model/access-grammar.d.ts +51 -1
- package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
- package/dist/modules/access-model/access-grammar.js +95 -1
- package/dist/modules/access-model/access-grammar.js.map +1 -1
- package/dist/modules/access-model/group-files.d.ts.map +1 -1
- package/dist/modules/access-model/group-files.js +4 -1
- package/dist/modules/access-model/group-files.js.map +1 -1
- package/dist/modules/access-model/kb-read-filter.d.ts +12 -0
- package/dist/modules/access-model/kb-read-filter.d.ts.map +1 -1
- package/dist/modules/access-model/kb-read-filter.js +15 -0
- package/dist/modules/access-model/kb-read-filter.js.map +1 -1
- package/dist/modules/access-model/plugin-principals.d.ts +49 -0
- package/dist/modules/access-model/plugin-principals.d.ts.map +1 -0
- package/dist/modules/access-model/plugin-principals.js +125 -0
- package/dist/modules/access-model/plugin-principals.js.map +1 -0
- package/dist/modules/connection-probe/connection-probe.contract.d.ts +43 -0
- package/dist/modules/connection-probe/connection-probe.contract.d.ts.map +1 -0
- package/dist/modules/connection-probe/connection-probe.contract.js +2 -0
- package/dist/modules/connection-probe/connection-probe.contract.js.map +1 -0
- package/dist/modules/connection-probe/connection-probe.service.d.ts +94 -0
- package/dist/modules/connection-probe/connection-probe.service.d.ts.map +1 -0
- package/dist/modules/connection-probe/connection-probe.service.js +684 -0
- package/dist/modules/connection-probe/connection-probe.service.js.map +1 -0
- package/dist/modules/connection-probe/index.d.ts +3 -0
- package/dist/modules/connection-probe/index.d.ts.map +1 -0
- package/dist/modules/connection-probe/index.js +3 -0
- package/dist/modules/connection-probe/index.js.map +1 -0
- package/dist/modules/database/core-schema.d.ts +326 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +52 -0
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/diff/diff.routes.d.ts.map +1 -1
- package/dist/modules/diff/diff.routes.js +3 -5
- package/dist/modules/diff/diff.routes.js.map +1 -1
- package/dist/modules/kb-fs/mutex.d.ts +37 -0
- package/dist/modules/kb-fs/mutex.d.ts.map +1 -1
- package/dist/modules/kb-fs/mutex.js +48 -5
- package/dist/modules/kb-fs/mutex.js.map +1 -1
- package/dist/modules/kb-sync/index.d.ts +6 -0
- package/dist/modules/kb-sync/index.d.ts.map +1 -0
- package/dist/modules/kb-sync/index.js +5 -0
- package/dist/modules/kb-sync/index.js.map +1 -0
- package/dist/modules/kb-sync/kb-sync.interface.d.ts +77 -0
- package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -0
- package/dist/modules/kb-sync/kb-sync.interface.js +2 -0
- package/dist/modules/kb-sync/kb-sync.interface.js.map +1 -0
- package/dist/modules/kb-sync/kb-sync.routes.d.ts +70 -0
- package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -0
- package/dist/modules/kb-sync/kb-sync.routes.js +135 -0
- package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -0
- package/dist/modules/kb-sync/kb-sync.service.d.ts +45 -0
- package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -0
- package/dist/modules/kb-sync/kb-sync.service.js +183 -0
- package/dist/modules/kb-sync/kb-sync.service.js.map +1 -0
- package/dist/modules/kb-sync/sync-auth.d.ts +59 -0
- package/dist/modules/kb-sync/sync-auth.d.ts.map +1 -0
- package/dist/modules/kb-sync/sync-auth.js +62 -0
- package/dist/modules/kb-sync/sync-auth.js.map +1 -0
- package/dist/modules/kb-sync/sync-payload.d.ts +32 -0
- package/dist/modules/kb-sync/sync-payload.d.ts.map +1 -0
- package/dist/modules/kb-sync/sync-payload.js +105 -0
- package/dist/modules/kb-sync/sync-payload.js.map +1 -0
- package/dist/modules/marketplace/git-http.routes.d.ts +42 -0
- package/dist/modules/marketplace/git-http.routes.d.ts.map +1 -0
- package/dist/modules/marketplace/git-http.routes.js +190 -0
- package/dist/modules/marketplace/git-http.routes.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts +23 -0
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js +59 -0
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-codes.store.d.ts +59 -0
- package/dist/modules/marketplace/github-facade/github-facade-codes.store.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-codes.store.js +96 -0
- package/dist/modules/marketplace/github-facade/github-facade-codes.store.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts +99 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js +147 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade.routes.d.ts +47 -0
- package/dist/modules/marketplace/github-facade/github-facade.routes.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade.routes.js +228 -0
- package/dist/modules/marketplace/github-facade/github-facade.routes.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade.service.d.ts +109 -0
- package/dist/modules/marketplace/github-facade/github-facade.service.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade.service.js +172 -0
- package/dist/modules/marketplace/github-facade/github-facade.service.js.map +1 -0
- package/dist/modules/marketplace/github-facade/index.d.ts +6 -0
- package/dist/modules/marketplace/github-facade/index.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/index.js +6 -0
- package/dist/modules/marketplace/github-facade/index.js.map +1 -0
- package/dist/modules/marketplace/index.d.ts +4 -0
- package/dist/modules/marketplace/index.d.ts.map +1 -0
- package/dist/modules/marketplace/index.js +4 -0
- package/dist/modules/marketplace/index.js.map +1 -0
- package/dist/modules/marketplace/marketplace-repo.service.d.ts +122 -0
- package/dist/modules/marketplace/marketplace-repo.service.d.ts.map +1 -0
- package/dist/modules/marketplace/marketplace-repo.service.js +300 -0
- package/dist/modules/marketplace/marketplace-repo.service.js.map +1 -0
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +4 -0
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts +14 -8
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js +13 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
- package/dist/modules/mcp/oauth/oauth-state.d.ts +13 -2
- package/dist/modules/mcp/oauth/oauth-state.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/oauth-state.js +3 -1
- package/dist/modules/mcp/oauth/oauth-state.js.map +1 -1
- package/dist/modules/plugins/compile/compile-marketplace.d.ts +97 -0
- package/dist/modules/plugins/compile/compile-marketplace.d.ts.map +1 -0
- package/dist/modules/plugins/compile/compile-marketplace.js +240 -0
- package/dist/modules/plugins/compile/compile-marketplace.js.map +1 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts +66 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts.map +1 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.js +132 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.js.map +1 -0
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.d.ts +37 -0
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js +135 -0
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js.map +1 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts +45 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.js +147 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.js.map +1 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.d.ts +37 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.js +107 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.js.map +1 -0
- package/dist/modules/plugins/discovery/native.source.d.ts +15 -0
- package/dist/modules/plugins/discovery/native.source.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/native.source.js +102 -0
- package/dist/modules/plugins/discovery/native.source.js.map +1 -0
- package/dist/modules/plugins/discovery/plugin-source.d.ts +96 -0
- package/dist/modules/plugins/discovery/plugin-source.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/plugin-source.js +2 -0
- package/dist/modules/plugins/discovery/plugin-source.js.map +1 -0
- package/dist/modules/plugins/index.d.ts +10 -1
- package/dist/modules/plugins/index.d.ts.map +1 -1
- package/dist/modules/plugins/index.js +8 -0
- package/dist/modules/plugins/index.js.map +1 -1
- package/dist/modules/plugins/plugin-links.d.ts +66 -0
- package/dist/modules/plugins/plugin-links.d.ts.map +1 -0
- package/dist/modules/plugins/plugin-links.js +160 -0
- package/dist/modules/plugins/plugin-links.js.map +1 -0
- package/dist/modules/plugins/plugin-links.service.d.ts +114 -0
- package/dist/modules/plugins/plugin-links.service.d.ts.map +1 -0
- package/dist/modules/plugins/plugin-links.service.js +304 -0
- package/dist/modules/plugins/plugin-links.service.js.map +1 -0
- package/dist/modules/plugins/plugin-provision.service.d.ts +39 -3
- package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.js +152 -52
- package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
- package/dist/modules/plugins/plugin-rename.service.d.ts +69 -0
- package/dist/modules/plugins/plugin-rename.service.d.ts.map +1 -0
- package/dist/modules/plugins/plugin-rename.service.js +245 -0
- package/dist/modules/plugins/plugin-rename.service.js.map +1 -0
- package/dist/modules/plugins/plugins.contract.d.ts +27 -1
- package/dist/modules/plugins/plugins.contract.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +7 -1
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +120 -12
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/plugins/plugins.service.d.ts +50 -3
- package/dist/modules/plugins/plugins.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.service.js +108 -47
- package/dist/modules/plugins/plugins.service.js.map +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.js +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +6 -0
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.js +31 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.d.ts +9 -0
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +82 -2
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/settings/setup.routes.d.ts +26 -0
- package/dist/modules/settings/setup.routes.d.ts.map +1 -1
- package/dist/modules/settings/setup.routes.js +9 -1
- package/dist/modules/settings/setup.routes.js.map +1 -1
- package/dist/modules/skills/index.d.ts +1 -0
- 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.map +1 -1
- package/dist/modules/skills/pending-skills.service.js +9 -4
- package/dist/modules/skills/pending-skills.service.js.map +1 -1
- package/dist/modules/skills/skill-access-requests.routes.d.ts +38 -0
- package/dist/modules/skills/skill-access-requests.routes.d.ts.map +1 -0
- package/dist/modules/skills/skill-access-requests.routes.js +168 -0
- package/dist/modules/skills/skill-access-requests.routes.js.map +1 -0
- package/dist/modules/skills/skills.contract.d.ts +31 -0
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.d.ts +28 -3
- package/dist/modules/skills/skills.routes.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.js +42 -3
- package/dist/modules/skills/skills.routes.js.map +1 -1
- package/dist/modules/skills/skills.service.d.ts +4 -0
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +83 -22
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.d.ts +18 -1
- package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.js +2 -1
- package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.d.ts +13 -4
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +26 -9
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts +36 -0
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.js +130 -83
- package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.js +10 -4
- package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +83 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +15 -2
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +199 -38
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +58 -3
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +325 -60
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.d.ts +6 -0
- package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.js +8 -0
- package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
- package/dist/modules/workflow/workflow.routes.d.ts +2 -1
- package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.routes.js +98 -9
- package/dist/modules/workflow/workflow.routes.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +133 -9
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +462 -52
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts +35 -0
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.js +121 -0
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.js.map +1 -0
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js +46 -2
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.js +112 -25
- package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +11 -14
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +38 -1
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +131 -20
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/shared/domain-errors.d.ts +20 -0
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +29 -0
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/fs-errors.d.ts +8 -0
- package/dist/shared/fs-errors.d.ts.map +1 -0
- package/dist/shared/fs-errors.js +11 -0
- package/dist/shared/fs-errors.js.map +1 -0
- package/dist/shared/fs-walk.d.ts +20 -6
- package/dist/shared/fs-walk.d.ts.map +1 -1
- package/dist/shared/fs-walk.js +35 -27
- package/dist/shared/fs-walk.js.map +1 -1
- package/dist/shared/kb-walk.d.ts +52 -0
- package/dist/shared/kb-walk.d.ts.map +1 -0
- package/dist/shared/kb-walk.js +76 -0
- package/dist/shared/kb-walk.js.map +1 -0
- package/dist/shared/path-order.d.ts +18 -0
- package/dist/shared/path-order.d.ts.map +1 -0
- package/dist/shared/path-order.js +35 -0
- package/dist/shared/path-order.js.map +1 -0
- package/dist/shared/token-crypto.d.ts +17 -2
- package/dist/shared/token-crypto.d.ts.map +1 -1
- package/dist/shared/token-crypto.js +25 -8
- package/dist/shared/token-crypto.js.map +1 -1
- package/dist/shared/ttl-cache.d.ts +28 -1
- package/dist/shared/ttl-cache.d.ts.map +1 -1
- package/dist/shared/ttl-cache.js +33 -1
- package/dist/shared/ttl-cache.js.map +1 -1
- package/kb-template/.bevelignore +1 -1
- package/kb-template/AGENTS.md +95 -30
- package/kb-template/Skills/.gitkeep +0 -0
- package/migrations/0005_github_facade.sql +25 -0
- package/migrations/meta/0005_snapshot.json +1717 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +6 -6
- package/src/__tests__/core-config.admin.test.ts +21 -0
- package/src/__tests__/kb-layout-config.test.ts +110 -0
- package/src/core/__tests__/catalog-cache-invalidation.test.ts +146 -0
- package/src/core/catalog-cache-invalidation.ts +92 -0
- package/src/core/create-core-server.ts +126 -7
- package/src/core/create-core-services.ts +173 -41
- package/src/core-config.ts +14 -7
- package/src/modules/access/__tests__/access-control.invalidate-race.test.ts +86 -0
- package/src/modules/access/__tests__/access-control.service.test.ts +19 -3
- package/src/modules/access/__tests__/access-plugin-principals.test.ts +368 -0
- package/src/modules/access/__tests__/groups-admin.service.test.ts +8 -0
- package/src/modules/access/access-control.interface.ts +20 -9
- package/src/modules/access/access-control.service.ts +240 -78
- package/src/modules/access/access.routes.ts +96 -14
- package/src/modules/access-model/__tests__/access-grammar.test.ts +18 -0
- package/src/modules/access-model/__tests__/plugin-token-canonical.test.ts +40 -0
- package/src/modules/access-model/access-grammar.ts +114 -2
- package/src/modules/access-model/group-files.ts +4 -0
- package/src/modules/access-model/kb-read-filter.ts +22 -0
- package/src/modules/access-model/plugin-principals.ts +137 -0
- package/src/modules/connection-probe/__tests__/connection-probe.service.test.ts +685 -0
- package/src/modules/connection-probe/connection-probe.contract.ts +44 -0
- package/src/modules/connection-probe/connection-probe.service.ts +734 -0
- package/src/modules/connection-probe/index.ts +2 -0
- package/src/modules/database/core-schema.ts +54 -0
- package/src/modules/diff/diff.routes.ts +9 -5
- package/src/modules/kb-fs/__tests__/mutex.test.ts +107 -0
- package/src/modules/kb-fs/mutex.ts +50 -5
- package/src/modules/kb-sync/__tests__/kb-sync.routes.test.ts +297 -0
- package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +295 -0
- package/src/modules/kb-sync/__tests__/sync-auth.test.ts +118 -0
- package/src/modules/kb-sync/__tests__/sync-payload.test.ts +148 -0
- package/src/modules/kb-sync/index.ts +19 -0
- package/src/modules/kb-sync/kb-sync.interface.ts +79 -0
- package/src/modules/kb-sync/kb-sync.routes.ts +197 -0
- package/src/modules/kb-sync/kb-sync.service.ts +201 -0
- package/src/modules/kb-sync/sync-auth.ts +118 -0
- package/src/modules/kb-sync/sync-payload.ts +143 -0
- package/src/modules/marketplace/__tests__/github-facade.test.ts +443 -0
- package/src/modules/marketplace/__tests__/marketplace-git.test.ts +179 -0
- package/src/modules/marketplace/git-http.routes.ts +193 -0
- package/src/modules/marketplace/github-facade/github-facade-admin.routes.ts +78 -0
- package/src/modules/marketplace/github-facade/github-facade-codes.store.ts +145 -0
- package/src/modules/marketplace/github-facade/github-facade-credentials.service.ts +208 -0
- package/src/modules/marketplace/github-facade/github-facade.routes.ts +268 -0
- package/src/modules/marketplace/github-facade/github-facade.service.ts +221 -0
- package/src/modules/marketplace/github-facade/index.ts +25 -0
- package/src/modules/marketplace/index.ts +3 -0
- package/src/modules/marketplace/marketplace-repo.service.ts +337 -0
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +3 -0
- package/src/modules/mcp/oauth/oauth-consent.routes.ts +24 -2
- package/src/modules/mcp/oauth/oauth-state.ts +15 -3
- package/src/modules/plugins/__tests__/bundle-dialect.e2e.test.ts +163 -0
- package/src/modules/plugins/__tests__/compile-marketplace.test.ts +294 -0
- package/src/modules/plugins/__tests__/plugin-index.service.test.ts +56 -12
- package/src/modules/plugins/__tests__/plugin-links.service.test.ts +287 -0
- package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +205 -5
- package/src/modules/plugins/__tests__/plugin-rename.service.test.ts +394 -0
- package/src/modules/plugins/__tests__/plugins.routes.test.ts +80 -33
- package/src/modules/plugins/compile/compile-marketplace.ts +403 -0
- package/src/modules/plugins/compile/marketplace-compiler.service.ts +150 -0
- package/src/modules/plugins/discovery/__tests__/kb-plugin-source.test.ts +329 -0
- package/src/modules/plugins/discovery/bundle-dialect/bundle.source.ts +140 -0
- package/src/modules/plugins/discovery/bundle-dialect/registry.ts +175 -0
- package/src/modules/plugins/discovery/kb-plugin-source.ts +114 -0
- package/src/modules/plugins/discovery/native.source.ts +122 -0
- package/src/modules/plugins/discovery/plugin-source.ts +99 -0
- package/src/modules/plugins/index.ts +10 -0
- package/src/modules/plugins/plugin-links.service.ts +363 -0
- package/src/modules/plugins/plugin-links.ts +184 -0
- package/src/modules/plugins/plugin-provision.service.ts +160 -54
- package/src/modules/plugins/plugin-rename.service.ts +298 -0
- package/src/modules/plugins/plugins.contract.ts +28 -1
- package/src/modules/plugins/plugins.routes.ts +128 -11
- package/src/modules/plugins/plugins.service.ts +106 -53
- package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +12 -0
- package/src/modules/secrets-vault/__tests__/oauth-return-to.route.test.ts +10 -3
- package/src/modules/secrets-vault/__tests__/tool-owner-gate.route.test.ts +26 -0
- package/src/modules/secrets-vault/db-secrets-vault.service.ts +1 -1
- package/src/modules/secrets-vault/secrets-vault.routes.ts +35 -1
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +57 -0
- package/src/modules/settings/__tests__/setup.routes.sync-status.test.ts +77 -0
- package/src/modules/settings/deployment-settings.service.ts +95 -2
- package/src/modules/settings/setup.routes.ts +24 -0
- package/src/modules/skills/__tests__/skill-access-requests.routes.test.ts +128 -0
- package/src/modules/skills/__tests__/skills.service.test.ts +85 -0
- package/src/modules/skills/index.ts +1 -0
- package/src/modules/skills/pending-skills.service.ts +7 -5
- package/src/modules/skills/skill-access-requests.routes.ts +199 -0
- package/src/modules/skills/skills.contract.ts +32 -0
- package/src/modules/skills/skills.routes.ts +59 -3
- package/src/modules/skills/skills.service.ts +84 -22
- package/src/modules/tool-auth/external-api-key.interface.ts +20 -1
- package/src/modules/tool-auth/external-api-key.service.ts +31 -13
- package/src/modules/tool-manuals/__tests__/mcp-json-discovery.test.ts +35 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.cli.test.ts +2 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.health-check.test.ts +104 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +56 -0
- package/src/modules/tool-manuals/mcp-json-discovery.ts +154 -76
- package/src/modules/tool-manuals/mcp-server-edit.service.ts +13 -4
- package/src/modules/tool-manuals/tool-manuals.contract.ts +89 -0
- package/src/modules/tool-manuals/tool-manuals.service.ts +210 -37
- package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +1 -1
- package/src/modules/workflow/__tests__/workflow.routes.history-read-gate.test.ts +139 -0
- package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +337 -0
- package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +1 -0
- package/src/modules/workflow/__tests__/workflow.service.deleted-branch-sweep.test.ts +7 -1
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +97 -10
- package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +119 -2
- package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +437 -0
- package/src/modules/workflow/git/__tests__/git.service.history-guards.test.ts +86 -0
- package/src/modules/workflow/git/__tests__/git.service.postMergePull.test.ts +3 -3
- package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +47 -4
- package/src/modules/workflow/git/__tests__/git.service.syncFromRemote.test.ts +253 -0
- package/src/modules/workflow/git/git.service.ts +354 -63
- package/src/modules/workflow/git/pull-request.service.ts +9 -0
- package/src/modules/workflow/workflow.routes.ts +104 -4
- package/src/modules/workflow/workflow.service.ts +505 -64
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +6 -7
- package/src/modules/workspace/__tests__/workspace.service.list-cloned.test.ts +93 -0
- package/src/modules/workspace/__tests__/workspace.service.missing-branch.test.ts +28 -0
- package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +42 -12
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +219 -4
- package/src/modules/workspace/startup/steps/plugin-manifests.step.ts +129 -0
- package/src/modules/workspace/startup/steps/seed-tree.ts +44 -2
- package/src/modules/workspace/startup/steps/template-files.step.ts +131 -27
- package/src/modules/workspace/workspace.routes.ts +17 -21
- package/src/modules/workspace/workspace.service.ts +128 -19
- package/src/shared/__tests__/filename.test.ts +0 -0
- package/src/shared/__tests__/fs-walk.test.ts +22 -1
- package/src/shared/__tests__/kb-walk.test.ts +107 -0
- package/src/shared/__tests__/path-order.test.ts +24 -0
- package/src/shared/__tests__/token-crypto.test.ts +34 -0
- package/src/shared/__tests__/ttl-cache.test.ts +55 -0
- package/src/shared/domain-errors.ts +29 -0
- package/src/shared/fs-errors.ts +10 -0
- package/src/shared/fs-walk.ts +38 -23
- package/src/shared/kb-walk.ts +95 -0
- package/src/shared/path-order.ts +33 -0
- package/src/shared/token-crypto.ts +28 -10
- package/src/shared/ttl-cache.ts +33 -1
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { drizzle } from 'drizzle-orm/pg-proxy';
|
|
3
|
+
import { configureBranchModel } from '@bevel-software/platform-shared';
|
|
4
|
+
import type { GitService } from '../git/git.service.js';
|
|
5
|
+
import type { PullRequestService } from '../git/pull-request.service.js';
|
|
6
|
+
import type { IReviewWorkflowService } from '../review-workflow/review-workflow.interface.js';
|
|
7
|
+
import type { WorkspaceService } from '../../workspace/workspace.service.js';
|
|
8
|
+
import type { IAccessControl } from '../../access/access-control.interface.js';
|
|
9
|
+
import type { FileLockService } from '../file-lock.service.js';
|
|
10
|
+
import type { PendingCommitsService } from '../pending-commits.service.js';
|
|
11
|
+
import type { Database } from '../../database/connection.js';
|
|
12
|
+
import { WorkflowService } from '../workflow.service.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A branch is load-bearing for a change request at EITHER end.
|
|
16
|
+
*
|
|
17
|
+
* The regression: merging `X -> main` retired X while a second open request
|
|
18
|
+
* `Y -> X` still proposed INTO it. Every guard asked only "is X anybody's
|
|
19
|
+
* SOURCE?", so X was deleted and `Y -> X` became permanently unusable — it
|
|
20
|
+
* could not be read (the detail read resolves the target first), declined, or
|
|
21
|
+
* deleted, and the boot sweep looked at sources only, so it survived every
|
|
22
|
+
* restart.
|
|
23
|
+
*
|
|
24
|
+
* These tests run the REAL drizzle query against seeded rows (via the pg-proxy
|
|
25
|
+
* driver, which hands the generated SQL and its parameters to a callback)
|
|
26
|
+
* rather than a stub that answers by call order. That is deliberate: a mock
|
|
27
|
+
* blind to the `where` clause returns the same row whether the guard asks
|
|
28
|
+
* about one branch end or both, so it would pass just as happily against the
|
|
29
|
+
* source-only code this fixes — it could not detect the regression it exists
|
|
30
|
+
* for. Here, reverting `openChangeRequestOn` to a source-only lookup makes the
|
|
31
|
+
* seeded `Y -> X` row stop matching, and these tests fail.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
interface CrRow {
|
|
35
|
+
number: number;
|
|
36
|
+
source_branch: string;
|
|
37
|
+
target_branch: string;
|
|
38
|
+
state: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A `change_requests` table that answers the query it is actually given.
|
|
43
|
+
*
|
|
44
|
+
* The driver receives real SQL and real parameters, so the predicate is read
|
|
45
|
+
* back off the statement: every `"column" = $n` becomes a (column, value)
|
|
46
|
+
* pair. One value bound to SEVERAL columns is the either-end test and ORs
|
|
47
|
+
* across them (`source_branch = $1 or target_branch = $1`); distinct values
|
|
48
|
+
* AND together (`… and state = $2`). Non-`select` statements report one
|
|
49
|
+
* affected row, which is the guarded update in the sweep succeeding.
|
|
50
|
+
*/
|
|
51
|
+
function fakeDb(rows: CrRow[]) {
|
|
52
|
+
const statements: string[] = [];
|
|
53
|
+
const db = drizzle(async (sql: string, params: unknown[]) => {
|
|
54
|
+
statements.push(sql);
|
|
55
|
+
if (!/^\s*select/i.test(sql)) return { rows: [['row-id']] };
|
|
56
|
+
|
|
57
|
+
const projection = (/^\s*select\s+(.+?)\s+from\s/is.exec(sql)?.[1] ?? '')
|
|
58
|
+
.split(',')
|
|
59
|
+
.map((item) => item.trim().replace(/.*"(\w+)"$/, '$1'));
|
|
60
|
+
|
|
61
|
+
const byValue = new Map<unknown, string[]>();
|
|
62
|
+
for (const m of sql.matchAll(/"(\w+)"\s*=\s*\$(\d+)/g)) {
|
|
63
|
+
const value = params[Number(m[2]) - 1];
|
|
64
|
+
byValue.set(value, [...(byValue.get(value) ?? []), m[1]!]);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Reading the predicate off the statement means the reader can go stale
|
|
68
|
+
// against a future drizzle that quotes, aliases or parameterises
|
|
69
|
+
// differently. That is survivable ONLY if it fails loudly: an unparsed
|
|
70
|
+
// WHERE would leave `byValue` empty, `every` over nothing is vacuously
|
|
71
|
+
// true, and every seeded row would match — which is exactly the blind
|
|
72
|
+
// mock this harness replaced, silently restored. So refuse to answer a
|
|
73
|
+
// statement this cannot actually read.
|
|
74
|
+
if (!projection.length || projection.some((c) => !/^\w+$/.test(c))) {
|
|
75
|
+
throw new Error(`fakeDb could not read the projection of: ${sql}`);
|
|
76
|
+
}
|
|
77
|
+
if (/\bwhere\b/i.test(sql) && byValue.size === 0) {
|
|
78
|
+
throw new Error(`fakeDb could not read the predicate of: ${sql}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const matched = rows.filter((row) =>
|
|
82
|
+
[...byValue].every(([value, columns]) =>
|
|
83
|
+
columns.some((c) => row[c as keyof CrRow] === value),
|
|
84
|
+
),
|
|
85
|
+
);
|
|
86
|
+
return { rows: matched.map((row) => projection.map((c) => row[c as keyof CrRow])) };
|
|
87
|
+
});
|
|
88
|
+
return { db: db as unknown as Database, statements };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function makeService(db: Database, git: Partial<GitService>) {
|
|
92
|
+
return new WorkflowService(
|
|
93
|
+
db,
|
|
94
|
+
git as GitService,
|
|
95
|
+
{ invalidateDetailCache: vi.fn() } as unknown as PullRequestService,
|
|
96
|
+
{} as IReviewWorkflowService,
|
|
97
|
+
{
|
|
98
|
+
getOrCreateForBranch: vi.fn(async () => ({ id: 'ws-target' })),
|
|
99
|
+
ensureRemotesFetched: vi.fn(async () => undefined),
|
|
100
|
+
hasBootstrappedWorkspace: vi.fn(async () => false),
|
|
101
|
+
} as unknown as WorkspaceService,
|
|
102
|
+
{} as IAccessControl,
|
|
103
|
+
{} as FileLockService,
|
|
104
|
+
{} as PendingCommitsService,
|
|
105
|
+
'knowledge-base',
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const USER = { email: 'juan@example.com', name: 'Juan', id: 'u1' };
|
|
110
|
+
|
|
111
|
+
/** The merged request, and a second one still proposing INTO its source. */
|
|
112
|
+
const MERGED_23: CrRow = {
|
|
113
|
+
number: 23,
|
|
114
|
+
source_branch: 'juan/rfi',
|
|
115
|
+
target_branch: 'target-company-state',
|
|
116
|
+
state: 'merged',
|
|
117
|
+
};
|
|
118
|
+
const OPEN_24_INTO_RFI: CrRow = {
|
|
119
|
+
number: 24,
|
|
120
|
+
source_branch: 'juan/rfi-mock',
|
|
121
|
+
target_branch: 'juan/rfi',
|
|
122
|
+
state: 'open',
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
function retire(svc: WorkflowService, number: number, base: string) {
|
|
126
|
+
return (
|
|
127
|
+
svc as unknown as {
|
|
128
|
+
retireMergedSourceBranch(n: number, base: string, u: unknown): Promise<void>;
|
|
129
|
+
}
|
|
130
|
+
).retireMergedSourceBranch(number, base, USER);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
beforeEach(() => {
|
|
134
|
+
configureBranchModel({
|
|
135
|
+
defaultBranch: 'target-company-state',
|
|
136
|
+
protectedBranches: ['current-company-state', 'target-company-state'],
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('post-merge branch retirement', () => {
|
|
141
|
+
it('keeps a branch another open request proposes INTO', async () => {
|
|
142
|
+
const deleted: string[] = [];
|
|
143
|
+
const { db, statements } = fakeDb([MERGED_23, OPEN_24_INTO_RFI]);
|
|
144
|
+
const svc = makeService(db, {
|
|
145
|
+
deleteBranch: vi.fn(async (_ws: string, name: string) => void deleted.push(name)),
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
await retire(svc, 23, 'target-company-state');
|
|
149
|
+
|
|
150
|
+
expect(deleted).toEqual([]);
|
|
151
|
+
// The guard must ask about both ends — a statement naming only
|
|
152
|
+
// `source_branch` is the regression.
|
|
153
|
+
expect(statements.some((q) => /target_branch/.test(q))).toBe(true);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('still retires a branch nothing open needs', async () => {
|
|
157
|
+
const deleted: string[] = [];
|
|
158
|
+
// #24 is gone; only the merged request remains, so nothing needs the branch.
|
|
159
|
+
const { db } = fakeDb([MERGED_23]);
|
|
160
|
+
const svc = makeService(db, {
|
|
161
|
+
deleteBranch: vi.fn(async (_ws: string, name: string) => void deleted.push(name)),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
await retire(svc, 23, 'target-company-state');
|
|
165
|
+
|
|
166
|
+
expect(deleted).toEqual(['juan/rfi']);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('keeps a branch another open request proposes FROM', async () => {
|
|
170
|
+
const deleted: string[] = [];
|
|
171
|
+
const { db } = fakeDb([
|
|
172
|
+
MERGED_23,
|
|
173
|
+
{ number: 25, source_branch: 'juan/rfi', target_branch: 'other', state: 'open' },
|
|
174
|
+
]);
|
|
175
|
+
const svc = makeService(db, {
|
|
176
|
+
deleteBranch: vi.fn(async (_ws: string, name: string) => void deleted.push(name)),
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
await retire(svc, 23, 'target-company-state');
|
|
180
|
+
|
|
181
|
+
expect(deleted).toEqual([]);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe('deleteBranch', () => {
|
|
186
|
+
it('refuses a branch an open request proposes INTO, naming who can act', async () => {
|
|
187
|
+
const { db } = fakeDb([OPEN_24_INTO_RFI]);
|
|
188
|
+
const svc = makeService(db, { deleteBranch: vi.fn() });
|
|
189
|
+
|
|
190
|
+
// Not "withdraw or decline it": the request belongs to someone else, and
|
|
191
|
+
// the branch owner may be authorized to do neither. The message has to
|
|
192
|
+
// say what the situation is and who can clear it.
|
|
193
|
+
await expect(svc.deleteBranch('ws', 'juan/rfi', USER)).rejects.toThrow(
|
|
194
|
+
/change request #24 proposes changes into "juan\/rfi"/,
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('refuses a branch an open request proposes FROM, offering the withdraw verb', async () => {
|
|
199
|
+
const { db } = fakeDb([
|
|
200
|
+
{ number: 26, source_branch: 'juan/rfi', target_branch: 'target-company-state', state: 'open' },
|
|
201
|
+
]);
|
|
202
|
+
const svc = makeService(db, { deleteBranch: vi.fn() });
|
|
203
|
+
|
|
204
|
+
await expect(svc.deleteBranch('ws', 'juan/rfi', USER)).rejects.toThrow(
|
|
205
|
+
/open change request \(#26\)\. Withdraw or decline it/,
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('allows a branch no open request names', async () => {
|
|
210
|
+
const deleted: string[] = [];
|
|
211
|
+
const { db } = fakeDb([OPEN_24_INTO_RFI]);
|
|
212
|
+
const svc = makeService(db, {
|
|
213
|
+
deleteBranch: vi.fn(async (_ws: string, name: string) => void deleted.push(name)),
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
await svc.deleteBranch('ws', 'juan/unrelated', USER);
|
|
217
|
+
|
|
218
|
+
expect(deleted).toEqual(['juan/unrelated']);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('runOnBranchPair', () => {
|
|
223
|
+
function deferred() {
|
|
224
|
+
let resolve!: () => void;
|
|
225
|
+
const promise = new Promise<void>((r) => { resolve = r; });
|
|
226
|
+
return { promise, resolve };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
type LockInternals = {
|
|
230
|
+
branchLifecycle: { run(key: string, fn: () => Promise<void>): Promise<void> };
|
|
231
|
+
runOnBranchPair(source: string, target: string, fn: () => Promise<void>): Promise<void>;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
function locks(svc: WorkflowService) {
|
|
235
|
+
return svc as unknown as LockInternals;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
it('waits on an unprotected target: its key really is reserved', async () => {
|
|
239
|
+
const { db } = fakeDb([]);
|
|
240
|
+
const svc = locks(makeService(db, {}));
|
|
241
|
+
const order: string[] = [];
|
|
242
|
+
const release = deferred();
|
|
243
|
+
|
|
244
|
+
const holder = svc.branchLifecycle.run('branch:juan/rfi', async () => {
|
|
245
|
+
await release.promise;
|
|
246
|
+
order.push('holder');
|
|
247
|
+
});
|
|
248
|
+
await new Promise((r) => setImmediate(r));
|
|
249
|
+
|
|
250
|
+
const pair = svc.runOnBranchPair('juan/other', 'juan/rfi', async () => {
|
|
251
|
+
order.push('pair');
|
|
252
|
+
});
|
|
253
|
+
await new Promise((r) => setImmediate(r));
|
|
254
|
+
expect(order).toEqual([]);
|
|
255
|
+
|
|
256
|
+
release.resolve();
|
|
257
|
+
await Promise.all([holder, pair]);
|
|
258
|
+
expect(order).toEqual(['holder', 'pair']);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('does not reserve a PROTECTED target key', async () => {
|
|
262
|
+
// No path in the system can delete a protected branch, so the key would
|
|
263
|
+
// exclude nothing while serializing every open into the default branch,
|
|
264
|
+
// which is where nearly all requests point. The pair operation must run
|
|
265
|
+
// even while something else holds the protected branch's key.
|
|
266
|
+
const { db } = fakeDb([]);
|
|
267
|
+
const svc = locks(makeService(db, {}));
|
|
268
|
+
const order: string[] = [];
|
|
269
|
+
const release = deferred();
|
|
270
|
+
|
|
271
|
+
const holder = svc.branchLifecycle.run('branch:target-company-state', async () => {
|
|
272
|
+
await release.promise;
|
|
273
|
+
order.push('holder');
|
|
274
|
+
});
|
|
275
|
+
await new Promise((r) => setImmediate(r));
|
|
276
|
+
|
|
277
|
+
await svc.runOnBranchPair('juan/other', 'target-company-state', async () => {
|
|
278
|
+
order.push('pair');
|
|
279
|
+
});
|
|
280
|
+
expect(order).toEqual(['pair']);
|
|
281
|
+
|
|
282
|
+
release.resolve();
|
|
283
|
+
await holder;
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('always reserves the source key', async () => {
|
|
287
|
+
const { db } = fakeDb([]);
|
|
288
|
+
const svc = locks(makeService(db, {}));
|
|
289
|
+
const order: string[] = [];
|
|
290
|
+
const release = deferred();
|
|
291
|
+
|
|
292
|
+
const holder = svc.branchLifecycle.run('branch:juan/other', async () => {
|
|
293
|
+
await release.promise;
|
|
294
|
+
order.push('holder');
|
|
295
|
+
});
|
|
296
|
+
await new Promise((r) => setImmediate(r));
|
|
297
|
+
|
|
298
|
+
const pair = svc.runOnBranchPair('juan/other', 'target-company-state', async () => {
|
|
299
|
+
order.push('pair');
|
|
300
|
+
});
|
|
301
|
+
await new Promise((r) => setImmediate(r));
|
|
302
|
+
expect(order).toEqual([]);
|
|
303
|
+
|
|
304
|
+
release.resolve();
|
|
305
|
+
await Promise.all([holder, pair]);
|
|
306
|
+
expect(order).toEqual(['holder', 'pair']);
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
describe('closeChangeRequestsWithDeletedBranches', () => {
|
|
311
|
+
it('closes a request whose TARGET branch is gone', async () => {
|
|
312
|
+
const { db } = fakeDb([OPEN_24_INTO_RFI]);
|
|
313
|
+
const svc = makeService(db, {
|
|
314
|
+
// #24's source lives on; its target `juan/rfi` is gone.
|
|
315
|
+
listBranches: vi.fn(async () => [
|
|
316
|
+
{ name: 'target-company-state' },
|
|
317
|
+
{ name: 'juan/rfi-mock' },
|
|
318
|
+
]),
|
|
319
|
+
} as unknown as Partial<GitService>);
|
|
320
|
+
|
|
321
|
+
await expect(svc.closeChangeRequestsWithDeletedBranches()).resolves.toBe(1);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('leaves a request whose branches both exist', async () => {
|
|
325
|
+
const { db } = fakeDb([
|
|
326
|
+
{ ...OPEN_24_INTO_RFI, target_branch: 'target-company-state' },
|
|
327
|
+
]);
|
|
328
|
+
const svc = makeService(db, {
|
|
329
|
+
listBranches: vi.fn(async () => [
|
|
330
|
+
{ name: 'target-company-state' },
|
|
331
|
+
{ name: 'juan/rfi-mock' },
|
|
332
|
+
]),
|
|
333
|
+
} as unknown as Partial<GitService>);
|
|
334
|
+
|
|
335
|
+
await expect(svc.closeChangeRequestsWithDeletedBranches()).resolves.toBe(0);
|
|
336
|
+
});
|
|
337
|
+
});
|
|
@@ -114,7 +114,13 @@ describe('closeChangeRequestsWithDeletedBranches', () => {
|
|
|
114
114
|
{ name: 'x' },
|
|
115
115
|
]);
|
|
116
116
|
await svc.closeChangeRequestsWithDeletedBranches();
|
|
117
|
-
expect(git.listBranches).toHaveBeenCalledWith('target-company-state', {
|
|
117
|
+
expect(git.listBranches).toHaveBeenCalledWith('target-company-state', {
|
|
118
|
+
freshFetch: true,
|
|
119
|
+
// Strict: a fetch that cannot be proven fresh throws instead of serving
|
|
120
|
+
// stale refs, and the sweep's catch closes nothing. Without this, a
|
|
121
|
+
// branch created after the clone's last good fetch reads as "deleted".
|
|
122
|
+
strictFetch: true,
|
|
123
|
+
});
|
|
118
124
|
});
|
|
119
125
|
|
|
120
126
|
/**
|
|
@@ -16,14 +16,61 @@ import { PullRebaseConflictError } from '../../../shared/domain-errors.js';
|
|
|
16
16
|
import type { Database } from '../../database/connection.js';
|
|
17
17
|
|
|
18
18
|
// `deleteBranch`'s open-request guard is the only DB touch these tests
|
|
19
|
-
// exercise
|
|
20
|
-
//
|
|
21
|
-
|
|
19
|
+
// exercise. The stub ANSWERS FROM THE WHERE-CLAUSE rather than returning the
|
|
20
|
+
// rows unfiltered — a guard that dropped the `state = 'open'` filter or asked
|
|
21
|
+
// about only one branch end used to pass these tests anyway, because any row
|
|
22
|
+
// came back regardless of the predicate.
|
|
23
|
+
//
|
|
24
|
+
// The evaluator walks the drizzle condition tree (duck-typing the
|
|
25
|
+
// SQL/Column/Param/StringChunk shapes) and evaluates it against the row:
|
|
26
|
+
// an `eq(column, value)` leaf compares the row's field, and a node combines
|
|
27
|
+
// its children with whichever connector its literal chunks carry (`and` /
|
|
28
|
+
// `or` — drizzle nests them, so one node never mixes both).
|
|
29
|
+
function rowMatches(cond: unknown, row: Record<string, unknown>): boolean {
|
|
30
|
+
// snake_case column → camelCase row field (the stubs carry row objects in
|
|
31
|
+
// the service's own field names).
|
|
32
|
+
const field = (col: string) => col.replace(/_([a-z])/g, (_, ch: string) => ch.toUpperCase());
|
|
33
|
+
const evalNode = (node: unknown): boolean => {
|
|
34
|
+
const chunks = (node as { queryChunks?: unknown[] }).queryChunks;
|
|
35
|
+
if (!Array.isArray(chunks)) return true;
|
|
36
|
+
const results: boolean[] = [];
|
|
37
|
+
let anyOr = false;
|
|
38
|
+
let pendingColumn: string | null = null;
|
|
39
|
+
for (const c of chunks) {
|
|
40
|
+
if (!c || typeof c !== 'object') continue;
|
|
41
|
+
if ('queryChunks' in c) {
|
|
42
|
+
results.push(evalNode(c));
|
|
43
|
+
} else if ('name' in c && 'keyAsName' in c) {
|
|
44
|
+
pendingColumn = String((c as { name: unknown }).name);
|
|
45
|
+
} else if ('encoder' in c && 'value' in c) {
|
|
46
|
+
if (pendingColumn !== null) {
|
|
47
|
+
results.push(row[field(pendingColumn)] === (c as { value: unknown }).value);
|
|
48
|
+
pendingColumn = null;
|
|
49
|
+
}
|
|
50
|
+
} else if ('value' in c) {
|
|
51
|
+
if (String((c as { value: unknown }).value).includes(' or ')) anyOr = true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// A node this walker cannot decompose (`inArray`, `isNull`, a future
|
|
55
|
+
// rewrite of the query) must NOT read as a match — `true` here would be
|
|
56
|
+
// the permissive direction, returning rows the real query excludes and
|
|
57
|
+
// masking exactly the guard regressions these tests exist to catch.
|
|
58
|
+
if (results.length === 0) return false;
|
|
59
|
+
return anyOr ? results.some(Boolean) : results.every(Boolean);
|
|
60
|
+
};
|
|
61
|
+
return evalNode(cond);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function makeDb(rows: Record<string, unknown>[] = []): Database {
|
|
65
|
+
let lastWhere: unknown;
|
|
22
66
|
const chain = {
|
|
23
67
|
select: vi.fn(() => chain),
|
|
24
68
|
from: vi.fn(() => chain),
|
|
25
|
-
where: vi.fn(() =>
|
|
26
|
-
|
|
69
|
+
where: vi.fn((cond: unknown) => {
|
|
70
|
+
lastWhere = cond;
|
|
71
|
+
return chain;
|
|
72
|
+
}),
|
|
73
|
+
limit: vi.fn(async () => rows.filter((r) => rowMatches(lastWhere, r))),
|
|
27
74
|
};
|
|
28
75
|
return chain as unknown as Database;
|
|
29
76
|
}
|
|
@@ -135,7 +182,7 @@ function makeGit(): GitService {
|
|
|
135
182
|
commit: vi.fn(),
|
|
136
183
|
push: vi.fn(),
|
|
137
184
|
fetch: vi.fn(),
|
|
138
|
-
pull: vi.fn(),
|
|
185
|
+
pull: vi.fn().mockResolvedValue({ treeChanged: true }),
|
|
139
186
|
diffStat: vi.fn(),
|
|
140
187
|
pendingChanges: vi.fn(),
|
|
141
188
|
resolveForkBase: vi.fn(),
|
|
@@ -435,11 +482,27 @@ describe('WorkflowService — file lock delegation', () => {
|
|
|
435
482
|
it('deleteBranch refuses while the branch carries an open change request', async () => {
|
|
436
483
|
const git = makeGit();
|
|
437
484
|
// One open request rides the branch — deleting it would strand the request.
|
|
438
|
-
|
|
485
|
+
// `sourceBranch` matters to the guard now: it decides whether the refusal
|
|
486
|
+
// says "withdraw yours" (source) or names the other request's actors (target).
|
|
487
|
+
const svc = new WorkflowService(makeDb([{ number: 7, sourceBranch: 'feat/x', targetBranch: 'dev', state: 'open' }]), git, makePrs(), makeReviewWorkflow(), makeWorkspaceService(), makeAccessControl(), makeFileLockService(), makePendingCommits(), 'knowledge-base');
|
|
439
488
|
await expect(svc.deleteBranch('w1', 'feat/x', makeUser())).rejects.toThrow(/open change request \(#7\)/);
|
|
440
489
|
expect(git.deleteBranch).not.toHaveBeenCalled();
|
|
441
490
|
});
|
|
442
491
|
|
|
492
|
+
it('deleteBranch refuses when an open request proposes INTO the branch (target end)', async () => {
|
|
493
|
+
const git = makeGit();
|
|
494
|
+
const svc = new WorkflowService(makeDb([{ number: 9, sourceBranch: 'other/y', targetBranch: 'feat/x', state: 'open' }]), git, makePrs(), makeReviewWorkflow(), makeWorkspaceService(), makeAccessControl(), makeFileLockService(), makePendingCommits(), 'knowledge-base');
|
|
495
|
+
await expect(svc.deleteBranch('w1', 'feat/x', makeUser())).rejects.toThrow(/proposes changes into/);
|
|
496
|
+
expect(git.deleteBranch).not.toHaveBeenCalled();
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
it('deleteBranch proceeds past a CLOSED request — the guard filters on state, not mere mention', async () => {
|
|
500
|
+
const git = makeGit();
|
|
501
|
+
const svc = new WorkflowService(makeDb([{ number: 7, sourceBranch: 'feat/x', targetBranch: 'dev', state: 'closed' }]), git, makePrs(), makeReviewWorkflow(), makeWorkspaceService(), makeAccessControl(), makeFileLockService(), makePendingCommits(), 'knowledge-base');
|
|
502
|
+
await expect(svc.deleteBranch('w1', 'feat/x', makeUser())).resolves.toBeUndefined();
|
|
503
|
+
expect(git.deleteBranch).toHaveBeenCalled();
|
|
504
|
+
});
|
|
505
|
+
|
|
443
506
|
it('deleteBranch deletes when the branch has no open change request', async () => {
|
|
444
507
|
const git = makeGit();
|
|
445
508
|
const svc = new WorkflowService(makeDb(), git, makePrs(), makeReviewWorkflow(), makeWorkspaceService(), makeAccessControl(), makeFileLockService(), makePendingCommits(), 'knowledge-base');
|
|
@@ -496,7 +559,7 @@ describe('WorkflowService — revertChangeRequestFile / closeEmptyChangeRequest'
|
|
|
496
559
|
|
|
497
560
|
function makeRevertGit(overrides: Record<string, unknown> = {}): GitService {
|
|
498
561
|
return Object.assign(makeGit(), {
|
|
499
|
-
pull: vi.fn().mockResolvedValue(
|
|
562
|
+
pull: vi.fn().mockResolvedValue({ treeChanged: true }),
|
|
500
563
|
changedPathsForPr: vi.fn().mockResolvedValue(['Docs/a.md', 'Docs/b.md']),
|
|
501
564
|
mergeBaseForPr: vi.fn().mockResolvedValue('mb-sha'),
|
|
502
565
|
restorePathFromRef: vi.fn().mockResolvedValue(undefined),
|
|
@@ -656,8 +719,9 @@ describe('WorkflowService — deleteChangeRequest (admin moderation verb)', () =
|
|
|
656
719
|
set: vi.fn(() => chain),
|
|
657
720
|
returning: vi.fn(async () => [{ id: 1 }]),
|
|
658
721
|
});
|
|
659
|
-
const
|
|
660
|
-
|
|
722
|
+
const workspaces = makeWorkspaceService();
|
|
723
|
+
const svc = new WorkflowService(chain as unknown as Database, git, prs, makeReviewWorkflow(), workspaces, access, makeFileLockService(), makePendingCommits(), 'knowledge-base');
|
|
724
|
+
return { svc, prs, db: chain, access, workspaces };
|
|
661
725
|
}
|
|
662
726
|
|
|
663
727
|
it('refuses a non-admin with 403 and touches nothing', async () => {
|
|
@@ -678,4 +742,27 @@ describe('WorkflowService — deleteChangeRequest (admin moderation verb)', () =
|
|
|
678
742
|
await expect(svc.deleteChangeRequest(9, makeUser())).rejects.toMatchObject({ status: 422 });
|
|
679
743
|
expect(db.update).not.toHaveBeenCalled();
|
|
680
744
|
});
|
|
745
|
+
|
|
746
|
+
it('kicks the deleted-branch sweep when the base workspace cannot be resolved', async () => {
|
|
747
|
+
const { svc, db, workspaces } = makeDeleteHarness({ isAdmin: true });
|
|
748
|
+
(workspaces.getOrCreateForBranch as ReturnType<typeof vi.fn>).mockRejectedValue(
|
|
749
|
+
new Error('Remote branch main not found in upstream origin'),
|
|
750
|
+
);
|
|
751
|
+
const sweep = vi.spyOn(svc, 'closeChangeRequestsWithDeletedBranches').mockResolvedValue(0);
|
|
752
|
+
|
|
753
|
+
// The failure still surfaces (it is not proven absence, so the verb must
|
|
754
|
+
// not pretend to succeed), but the sweep is kicked so a genuinely
|
|
755
|
+
// stranded request is closed without waiting for a restart.
|
|
756
|
+
await expect(svc.deleteChangeRequest(9, makeUser())).rejects.toThrow(/not found/);
|
|
757
|
+
expect(sweep).toHaveBeenCalledTimes(1);
|
|
758
|
+
expect(db.update).not.toHaveBeenCalled();
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
it('does not kick the sweep on a plain authorization refusal', async () => {
|
|
762
|
+
const { svc, db } = makeDeleteHarness({ isAdmin: false });
|
|
763
|
+
const sweep = vi.spyOn(svc, 'closeChangeRequestsWithDeletedBranches').mockResolvedValue(0);
|
|
764
|
+
await expect(svc.deleteChangeRequest(9, makeUser())).rejects.toMatchObject({ status: 403 });
|
|
765
|
+
expect(sweep).not.toHaveBeenCalled();
|
|
766
|
+
expect(db.update).not.toHaveBeenCalled();
|
|
767
|
+
});
|
|
681
768
|
});
|
|
@@ -8,9 +8,10 @@ import type { IAccessControl } from '../../access/access-control.interface.js';
|
|
|
8
8
|
import { FileLockService } from '../file-lock.service.js';
|
|
9
9
|
import { PendingCommitsService } from '../pending-commits.service.js';
|
|
10
10
|
import { WorkflowEventBus } from '../event-bus.js';
|
|
11
|
-
import { WorkflowService } from '../workflow.service.js';
|
|
11
|
+
import { WorkflowService, syncConflictMessage } from '../workflow.service.js';
|
|
12
12
|
import type { Database } from '../../database/connection.js';
|
|
13
13
|
import {
|
|
14
|
+
PullRebaseConflictError,
|
|
14
15
|
PullRebaseConflictError,
|
|
15
16
|
PushNeedsAgentResolutionError,
|
|
16
17
|
WorkflowValidationError,
|
|
@@ -87,7 +88,7 @@ function makePending(): PendingCommitsService {
|
|
|
87
88
|
function makeGit(overrides: Partial<{
|
|
88
89
|
commitFile: Change | null;
|
|
89
90
|
pushBehavior: 'ok' | 'nff' | 'auth-fail';
|
|
90
|
-
pullBehavior: 'ok' | 'fail';
|
|
91
|
+
pullBehavior: 'ok' | 'fail' | 'conflict';
|
|
91
92
|
pushAfterPullBehavior: 'ok' | 'fail';
|
|
92
93
|
hasUnpushedCommits: boolean;
|
|
93
94
|
}> = {}): GitService {
|
|
@@ -120,6 +121,8 @@ function makeGit(overrides: Partial<{
|
|
|
120
121
|
}),
|
|
121
122
|
pull: vi.fn().mockImplementation(async () => {
|
|
122
123
|
if (pullBehavior === 'fail') throw new Error('git pull failed: merge conflict');
|
|
124
|
+
if (pullBehavior === 'conflict') throw new PullRebaseConflictError('feat/x', ['foo.md'], 'CONFLICT (content)');
|
|
125
|
+
return { treeChanged: true };
|
|
123
126
|
}),
|
|
124
127
|
} as unknown as GitService;
|
|
125
128
|
}
|
|
@@ -460,6 +463,29 @@ describe('WorkflowService.runPendingCommit — worker entry point', () => {
|
|
|
460
463
|
]);
|
|
461
464
|
});
|
|
462
465
|
|
|
466
|
+
it('a rebase CONFLICT on the recovery path raises the banner WITH its files, like the remote sync does', async () => {
|
|
467
|
+
// The banner keeps the latest failure per branch. The remote sync raises
|
|
468
|
+
// the conflict variant (files as links); if this path then raised the
|
|
469
|
+
// generic variant for the same conflict, the retry would take the links
|
|
470
|
+
// away — seen on staging. Every path names the files.
|
|
471
|
+
const git = makeGit({
|
|
472
|
+
commitFile: null,
|
|
473
|
+
hasUnpushedCommits: true,
|
|
474
|
+
pushBehavior: 'nff',
|
|
475
|
+
pullBehavior: 'conflict',
|
|
476
|
+
});
|
|
477
|
+
const svc = makeFacade(git, makeFileLocks(USER.id), makePending(), events);
|
|
478
|
+
await expect(svc.runPendingCommit('ws-1', 'feat/x', 'foo.md', USER)).rejects.toBeInstanceOf(
|
|
479
|
+
PushNeedsAgentResolutionError,
|
|
480
|
+
);
|
|
481
|
+
const failed = emitSpy.mock.calls.map((c) => c[0] as Record<string, unknown>).find((e) => e.kind === 'git-sync-failed');
|
|
482
|
+
expect(failed).toMatchObject({
|
|
483
|
+
branch: 'feat/x',
|
|
484
|
+
conflictedPaths: ['foo.md'],
|
|
485
|
+
reason: syncConflictMessage('feat/x', ['foo.md']),
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
|
|
463
489
|
it('recovers from non-fast-forward push via pull --rebase + retry, emits file-changed exactly once', async () => {
|
|
464
490
|
// Teammate pushed to the same branch between our commit and our
|
|
465
491
|
// push. The cooperative path pulls + retries — this is the case
|
|
@@ -750,3 +776,94 @@ describe('WorkflowService.updateFromRemote — pull-conflict recovery dispatch',
|
|
|
750
776
|
await expect(svc.updateFromRemote('main', USER)).rejects.toBe(CONFLICT);
|
|
751
777
|
});
|
|
752
778
|
});
|
|
779
|
+
|
|
780
|
+
describe('WorkflowService.updateFromRemote — the tree-change announcement', () => {
|
|
781
|
+
/**
|
|
782
|
+
* The announcement under test is what keeps the DEFAULT branch's catalog
|
|
783
|
+
* caches (skills, tool manuals, plugin index) honest: a pull is the one way
|
|
784
|
+
* a tree changes without passing the write routes, so `pullWorkspace` owes
|
|
785
|
+
* the process an `fs-tree-changed` — and owes it ONLY when the pull moved
|
|
786
|
+
* HEAD, on the default branch, after the pull resolved. Asserted through
|
|
787
|
+
* `updateFromRemote`, the public verb that wraps `pullWorkspace`.
|
|
788
|
+
*/
|
|
789
|
+
const DEFAULT_WS = 'target-company-state'; // == the test env's DEFAULT_BRANCH
|
|
790
|
+
|
|
791
|
+
function makePullingGit(result: { treeChanged: boolean } | Error): {
|
|
792
|
+
git: GitService;
|
|
793
|
+
pullSettled: () => boolean;
|
|
794
|
+
} {
|
|
795
|
+
let settled = false;
|
|
796
|
+
const git = {
|
|
797
|
+
pull: vi.fn().mockImplementation(async () => {
|
|
798
|
+
// A microtask gap, so an emit issued before the await would be
|
|
799
|
+
// observably premature rather than coincidentally ordered.
|
|
800
|
+
await Promise.resolve();
|
|
801
|
+
settled = true;
|
|
802
|
+
if (result instanceof Error) throw result;
|
|
803
|
+
return result;
|
|
804
|
+
}),
|
|
805
|
+
} as unknown as GitService;
|
|
806
|
+
return { git, pullSettled: () => settled };
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function treeChangedEvents(emitSpy: MockInstance): unknown[] {
|
|
810
|
+
return emitSpy.mock.calls
|
|
811
|
+
.map((c) => c[0] as { kind: string })
|
|
812
|
+
.filter((e) => e.kind === 'fs-tree-changed');
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
it('emits fs-tree-changed for the default workspace once the pull has changed the tree', async () => {
|
|
816
|
+
const events = new WorkflowEventBus();
|
|
817
|
+
const emitSpy = vi.spyOn(events, 'emit');
|
|
818
|
+
const { git, pullSettled } = makePullingGit({ treeChanged: true });
|
|
819
|
+
let settledAtEmit: boolean | undefined;
|
|
820
|
+
emitSpy.mockImplementation(() => {
|
|
821
|
+
settledAtEmit = pullSettled();
|
|
822
|
+
});
|
|
823
|
+
const svc = makeFacade(git, makeFileLocks(USER.id), makePending(), events);
|
|
824
|
+
|
|
825
|
+
await svc.updateFromRemote(DEFAULT_WS, USER);
|
|
826
|
+
|
|
827
|
+
expect(treeChangedEvents(emitSpy)).toEqual([
|
|
828
|
+
{ kind: 'fs-tree-changed', workspaceId: DEFAULT_WS, branch: DEFAULT_WS },
|
|
829
|
+
]);
|
|
830
|
+
// After the pull RESOLVED — an emit for a pull that later failed would
|
|
831
|
+
// make other clients refetch a tree that never changed.
|
|
832
|
+
expect(settledAtEmit).toBe(true);
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
it('stays silent when the pull found nothing new (treeChanged: false)', async () => {
|
|
836
|
+
const events = new WorkflowEventBus();
|
|
837
|
+
const emitSpy = vi.spyOn(events, 'emit');
|
|
838
|
+
const { git } = makePullingGit({ treeChanged: false });
|
|
839
|
+
const svc = makeFacade(git, makeFileLocks(USER.id), makePending(), events);
|
|
840
|
+
|
|
841
|
+
await svc.updateFromRemote(DEFAULT_WS, USER);
|
|
842
|
+
|
|
843
|
+
// An "already up to date" sync must not drop the catalogs or make every
|
|
844
|
+
// attached browser refetch its file tree.
|
|
845
|
+
expect(treeChangedEvents(emitSpy)).toEqual([]);
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
it('stays silent for a non-default workspace even when the tree changed', async () => {
|
|
849
|
+
const events = new WorkflowEventBus();
|
|
850
|
+
const emitSpy = vi.spyOn(events, 'emit');
|
|
851
|
+
const { git } = makePullingGit({ treeChanged: true });
|
|
852
|
+
const svc = makeFacade(git, makeFileLocks(USER.id), makePending(), events);
|
|
853
|
+
|
|
854
|
+
await svc.updateFromRemote(encodeURIComponent('alice/draft'), USER);
|
|
855
|
+
|
|
856
|
+
expect(treeChangedEvents(emitSpy)).toEqual([]);
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
it('stays silent when the pull throws', async () => {
|
|
860
|
+
const events = new WorkflowEventBus();
|
|
861
|
+
const emitSpy = vi.spyOn(events, 'emit');
|
|
862
|
+
const { git } = makePullingGit(new Error('git fetch failed: could not resolve host'));
|
|
863
|
+
const svc = makeFacade(git, makeFileLocks(USER.id), makePending(), events);
|
|
864
|
+
|
|
865
|
+
await expect(svc.updateFromRemote(DEFAULT_WS, USER)).rejects.toThrow();
|
|
866
|
+
|
|
867
|
+
expect(treeChangedEvents(emitSpy)).toEqual([]);
|
|
868
|
+
});
|
|
869
|
+
});
|