@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,295 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import type { BranchSyncOutcome } from '@bevel-software/platform-shared';
|
|
3
|
+
import { KbSyncService } from '../kb-sync.service.js';
|
|
4
|
+
import type { SyncWorkflowPort, SyncWorkspacePort } from '../kb-sync.interface.js';
|
|
5
|
+
|
|
6
|
+
function deferred<T>() {
|
|
7
|
+
let resolve!: (v: T) => void;
|
|
8
|
+
const promise = new Promise<T>((res) => {
|
|
9
|
+
resolve = res;
|
|
10
|
+
});
|
|
11
|
+
return { promise, resolve };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function workspaces(branches: string[]): SyncWorkspacePort {
|
|
15
|
+
return {
|
|
16
|
+
listClonedWorkspaces: async () => branches.map((branch) => ({ id: encodeURIComponent(branch), branch })),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function updated(branch: string): BranchSyncOutcome {
|
|
21
|
+
return { branch, outcome: 'updated', from: 'aaa', to: 'bbb' };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe('KbSyncService', () => {
|
|
25
|
+
it('syncs every known clone for an "all" request and reports unknown branches as not cloned', async () => {
|
|
26
|
+
const workflow: SyncWorkflowPort = {
|
|
27
|
+
syncWorkspaceFromRemote: vi.fn(async (id: string) => updated(decodeURIComponent(id))),
|
|
28
|
+
closeChangeRequestsWithDeletedBranches: vi.fn(async () => 1),
|
|
29
|
+
retireRemoteGoneClone: vi.fn(async () => true),
|
|
30
|
+
};
|
|
31
|
+
const svc = new KbSyncService(workflow, workspaces(['main', 'ali/x']));
|
|
32
|
+
|
|
33
|
+
const all = await svc.sync({ branches: 'all' });
|
|
34
|
+
expect(all.status).toBe('synced');
|
|
35
|
+
expect(all.results.map((r) => r.branch)).toEqual(['main', 'ali/x']);
|
|
36
|
+
expect(all.changeRequests.closedDeletedBranch).toBe(1);
|
|
37
|
+
expect(workflow.syncWorkspaceFromRemote).toHaveBeenCalledWith('ali%2Fx');
|
|
38
|
+
|
|
39
|
+
const some = await svc.sync({ branches: ['ali/x', 'juan/other'] });
|
|
40
|
+
expect(some.results).toEqual([updated('ali/x'), { branch: 'juan/other', outcome: 'not-cloned' }]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('a conflict or an error makes the result partial; the other branches still sync', async () => {
|
|
44
|
+
const workflow: SyncWorkflowPort = {
|
|
45
|
+
syncWorkspaceFromRemote: vi.fn(async (id: string): Promise<BranchSyncOutcome> =>
|
|
46
|
+
id === 'main'
|
|
47
|
+
? { branch: 'main', outcome: 'conflict', conflictedPaths: ['a.md'], error: 'msg' }
|
|
48
|
+
: updated(decodeURIComponent(id)),
|
|
49
|
+
),
|
|
50
|
+
closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
|
|
51
|
+
retireRemoteGoneClone: vi.fn(async () => true),
|
|
52
|
+
};
|
|
53
|
+
const svc = new KbSyncService(workflow, workspaces(['main', 'ali/x']));
|
|
54
|
+
const r = await svc.sync({ branches: 'all' });
|
|
55
|
+
expect(r.status).toBe('partial');
|
|
56
|
+
expect(r.results[1]).toEqual(updated('ali/x'));
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('a failing sweep does not fail the sync', async () => {
|
|
60
|
+
const workflow: SyncWorkflowPort = {
|
|
61
|
+
syncWorkspaceFromRemote: vi.fn(async () => updated('main')),
|
|
62
|
+
closeChangeRequestsWithDeletedBranches: vi.fn(async () => {
|
|
63
|
+
throw new Error('origin down');
|
|
64
|
+
}),
|
|
65
|
+
retireRemoteGoneClone: vi.fn(async () => true),
|
|
66
|
+
};
|
|
67
|
+
const svc = new KbSyncService(workflow, workspaces(['main']));
|
|
68
|
+
const r = await svc.sync({ branches: 'all' });
|
|
69
|
+
expect(r.status).toBe('synced');
|
|
70
|
+
expect(r.changeRequests.closedDeletedBranch).toBe(0);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('coalesces everything that arrives mid-run into ONE follow-up carrying the union', async () => {
|
|
74
|
+
const gate = deferred<void>();
|
|
75
|
+
// Each run's branches, in order. The first run parks on `gate` so the
|
|
76
|
+
// others arrive mid-run; a new run is recognised by its first branch
|
|
77
|
+
// landing after the gate opened.
|
|
78
|
+
const calls: string[][] = [['']];
|
|
79
|
+
let gateOpened = false;
|
|
80
|
+
const workflow: SyncWorkflowPort = {
|
|
81
|
+
syncWorkspaceFromRemote: vi.fn(async (id: string) => {
|
|
82
|
+
const branch = decodeURIComponent(id);
|
|
83
|
+
if (!gateOpened && branch === 'main') {
|
|
84
|
+
await gate.promise;
|
|
85
|
+
gateOpened = true;
|
|
86
|
+
calls[0] = [];
|
|
87
|
+
} else if (gateOpened && calls.length === 1) {
|
|
88
|
+
calls.push([]);
|
|
89
|
+
}
|
|
90
|
+
calls[calls.length - 1].push(branch);
|
|
91
|
+
return updated(branch);
|
|
92
|
+
}),
|
|
93
|
+
closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
|
|
94
|
+
retireRemoteGoneClone: vi.fn(async () => true),
|
|
95
|
+
};
|
|
96
|
+
const svc = new KbSyncService(workflow, workspaces(['main', 'ali/x', 'juan/y']));
|
|
97
|
+
|
|
98
|
+
const first = svc.sync({ branches: ['main'] });
|
|
99
|
+
// Three requests land while the first is parked on `gate`.
|
|
100
|
+
const second = svc.sync({ branches: ['ali/x'] });
|
|
101
|
+
const third = svc.sync({ branches: ['juan/y'] });
|
|
102
|
+
const fourth = svc.sync({ branches: ['ali/x'] });
|
|
103
|
+
gate.resolve();
|
|
104
|
+
|
|
105
|
+
const [r1, r2, r3, r4] = await Promise.all([first, second, third, fourth]);
|
|
106
|
+
expect(r1.results.map((r) => r.branch)).toEqual(['main']);
|
|
107
|
+
// One follow-up, the union, in arrival order — and every mid-run caller
|
|
108
|
+
// answered for the branches IT asked about, never for the others'.
|
|
109
|
+
expect(calls).toEqual([['main'], ['ali/x', 'juan/y']]);
|
|
110
|
+
expect(r2.results.map((r) => r.branch)).toEqual(['ali/x']);
|
|
111
|
+
expect(r3.results.map((r) => r.branch)).toEqual(['juan/y']);
|
|
112
|
+
expect(r4.results.map((r) => r.branch)).toEqual(['ali/x']);
|
|
113
|
+
expect(workflow.closeChangeRequestsWithDeletedBranches).toHaveBeenCalledTimes(2);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('an "all" arriving mid-run absorbs the queued branches', async () => {
|
|
117
|
+
const gate = deferred<void>();
|
|
118
|
+
const workflow: SyncWorkflowPort = {
|
|
119
|
+
syncWorkspaceFromRemote: vi.fn(async (id: string) => {
|
|
120
|
+
if (id === 'main') await gate.promise;
|
|
121
|
+
return updated(decodeURIComponent(id));
|
|
122
|
+
}),
|
|
123
|
+
closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
|
|
124
|
+
retireRemoteGoneClone: vi.fn(async () => true),
|
|
125
|
+
};
|
|
126
|
+
const svc = new KbSyncService(workflow, workspaces(['main', 'ali/x', 'juan/y']));
|
|
127
|
+
const first = svc.sync({ branches: ['main'] });
|
|
128
|
+
const second = svc.sync({ branches: ['ali/x'] });
|
|
129
|
+
const third = svc.sync({ branches: 'all' });
|
|
130
|
+
gate.resolve();
|
|
131
|
+
await first;
|
|
132
|
+
// The follow-up pulled everything; the "all" caller hears all of it, the
|
|
133
|
+
// one-branch caller only its branch.
|
|
134
|
+
expect((await third).results.map((x) => x.branch)).toEqual(['main', 'ali/x', 'juan/y']);
|
|
135
|
+
expect((await second).results.map((x) => x.branch)).toEqual(['ali/x']);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('KbSyncService.lastSync', () => {
|
|
140
|
+
it('is null before the first sync, then records when, who and what', async () => {
|
|
141
|
+
const workflow: SyncWorkflowPort = {
|
|
142
|
+
syncWorkspaceFromRemote: vi.fn(async (id: string) => updated(decodeURIComponent(id))),
|
|
143
|
+
closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
|
|
144
|
+
retireRemoteGoneClone: vi.fn(async () => true),
|
|
145
|
+
};
|
|
146
|
+
let clock = 1_000;
|
|
147
|
+
const svc = new KbSyncService(workflow, workspaces(['main']), () => clock);
|
|
148
|
+
expect(svc.lastSync()).toBeNull();
|
|
149
|
+
|
|
150
|
+
await svc.sync({ branches: 'all', by: 'bearer' });
|
|
151
|
+
expect(svc.lastSync()).toEqual({
|
|
152
|
+
at: 1_000,
|
|
153
|
+
by: 'bearer',
|
|
154
|
+
status: 'synced',
|
|
155
|
+
results: [updated('main')],
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
clock = 2_000;
|
|
159
|
+
await svc.sync({ branches: ['main'], by: 'admin@example.com' });
|
|
160
|
+
expect(svc.lastSync()).toMatchObject({ at: 2_000, by: 'admin@example.com' });
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('a coalesced follow-up names everyone who asked for it', async () => {
|
|
164
|
+
const gate = deferred<void>();
|
|
165
|
+
const workflow: SyncWorkflowPort = {
|
|
166
|
+
syncWorkspaceFromRemote: vi.fn(async (id: string) => {
|
|
167
|
+
if (id === 'main') await gate.promise;
|
|
168
|
+
return updated(decodeURIComponent(id));
|
|
169
|
+
}),
|
|
170
|
+
closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
|
|
171
|
+
retireRemoteGoneClone: vi.fn(async () => true),
|
|
172
|
+
};
|
|
173
|
+
const svc = new KbSyncService(workflow, workspaces(['main', 'ali/x']));
|
|
174
|
+
const first = svc.sync({ branches: ['main'], by: 'bearer' });
|
|
175
|
+
const second = svc.sync({ branches: ['ali/x'], by: 'github-signature' });
|
|
176
|
+
const third = svc.sync({ branches: ['ali/x'], by: 'admin@example.com' });
|
|
177
|
+
gate.resolve();
|
|
178
|
+
await Promise.all([first, second, third]);
|
|
179
|
+
expect(svc.lastSync()?.by).toBe('github-signature, admin@example.com');
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('KbSyncService — a throwing branch step', () => {
|
|
184
|
+
it('becomes an error outcome; the other branches, the sweep and the record all still happen', async () => {
|
|
185
|
+
const workflow: SyncWorkflowPort = {
|
|
186
|
+
syncWorkspaceFromRemote: vi.fn(async (id: string) => {
|
|
187
|
+
if (id === 'main') throw new Error('fatal: unable to access https://x:ghp_secret@host/r');
|
|
188
|
+
return updated(decodeURIComponent(id));
|
|
189
|
+
}),
|
|
190
|
+
closeChangeRequestsWithDeletedBranches: vi.fn(async () => 1),
|
|
191
|
+
retireRemoteGoneClone: vi.fn(async () => true),
|
|
192
|
+
};
|
|
193
|
+
const svc = new KbSyncService(workflow, workspaces(['main', 'ali/x']));
|
|
194
|
+
const r = await svc.sync({ branches: 'all', by: 'bearer' });
|
|
195
|
+
expect(r.status).toBe('partial');
|
|
196
|
+
expect(r.results[0]).toMatchObject({ branch: 'main', outcome: 'error' });
|
|
197
|
+
expect((r.results[0] as { error: string }).error).not.toContain('ghp_secret');
|
|
198
|
+
expect(r.results[1]).toEqual(updated('ali/x'));
|
|
199
|
+
expect(r.changeRequests.closedDeletedBranch).toBe(1);
|
|
200
|
+
expect(svc.lastSync()?.status).toBe('partial');
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
describe('KbSyncService — clones the host no longer has, and clones that cannot be read', () => {
|
|
205
|
+
it('a remote-gone branch is retired through the workflow, counts as clean, and does not stop the others', async () => {
|
|
206
|
+
const workflow: SyncWorkflowPort = {
|
|
207
|
+
syncWorkspaceFromRemote: vi.fn(async (id: string): Promise<BranchSyncOutcome> =>
|
|
208
|
+
id === 'ali%2Fx' ? { branch: 'ali/x', outcome: 'remote-gone' } : updated(decodeURIComponent(id)),
|
|
209
|
+
),
|
|
210
|
+
closeChangeRequestsWithDeletedBranches: vi.fn(async () => 1),
|
|
211
|
+
retireRemoteGoneClone: vi.fn(async () => true),
|
|
212
|
+
};
|
|
213
|
+
const svc = new KbSyncService(workflow, workspaces(['ali/x', 'main']));
|
|
214
|
+
const r = await svc.sync({ branches: 'all' });
|
|
215
|
+
expect(r.status).toBe('synced');
|
|
216
|
+
expect(r.results).toEqual([{ branch: 'ali/x', outcome: 'remote-gone' }, updated('main')]);
|
|
217
|
+
expect(workflow.retireRemoteGoneClone).toHaveBeenCalledTimes(1);
|
|
218
|
+
expect(workflow.retireRemoteGoneClone).toHaveBeenCalledWith('ali%2Fx');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('a clone that could not be read is that branch’s error, not everyone’s', async () => {
|
|
222
|
+
const workflow: SyncWorkflowPort = {
|
|
223
|
+
syncWorkspaceFromRemote: vi.fn(async (id: string) => updated(decodeURIComponent(id))),
|
|
224
|
+
closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
|
|
225
|
+
retireRemoteGoneClone: vi.fn(async () => true),
|
|
226
|
+
};
|
|
227
|
+
const port: SyncWorkspacePort = {
|
|
228
|
+
listClonedWorkspaces: async () => [
|
|
229
|
+
{ id: 'main', branch: 'main' },
|
|
230
|
+
{ id: 'juan%2Fy', branch: 'juan/y', unreadable: "Could not read this branch's clone: EPERM" },
|
|
231
|
+
],
|
|
232
|
+
};
|
|
233
|
+
const svc = new KbSyncService(workflow, port);
|
|
234
|
+
const r = await svc.sync({ branches: 'all' });
|
|
235
|
+
expect(r.status).toBe('partial');
|
|
236
|
+
expect(r.results).toEqual([
|
|
237
|
+
updated('main'),
|
|
238
|
+
{ branch: 'juan/y', outcome: 'error', error: "Could not read this branch's clone: EPERM" },
|
|
239
|
+
]);
|
|
240
|
+
expect(workflow.syncWorkspaceFromRemote).toHaveBeenCalledTimes(1);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe('KbSyncService — a coalesced caller is judged by its own branches', () => {
|
|
245
|
+
it("branch A's hook is not failed by branch B's conflict in the same follow-up", async () => {
|
|
246
|
+
const gate = deferred<void>();
|
|
247
|
+
const workflow: SyncWorkflowPort = {
|
|
248
|
+
syncWorkspaceFromRemote: vi.fn(async (id: string): Promise<BranchSyncOutcome> => {
|
|
249
|
+
if (id === 'main') await gate.promise;
|
|
250
|
+
if (id === 'juan%2Fy') return { branch: 'juan/y', outcome: 'conflict', conflictedPaths: ['a.md'], error: 'msg' };
|
|
251
|
+
return updated(decodeURIComponent(id));
|
|
252
|
+
}),
|
|
253
|
+
closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
|
|
254
|
+
retireRemoteGoneClone: vi.fn(async () => true),
|
|
255
|
+
};
|
|
256
|
+
const svc = new KbSyncService(workflow, workspaces(['main', 'ali/x', 'juan/y']));
|
|
257
|
+
const first = svc.sync({ branches: ['main'] });
|
|
258
|
+
const hookA = svc.sync({ branches: ['ali/x'] });
|
|
259
|
+
const hookB = svc.sync({ branches: ['juan/y'] });
|
|
260
|
+
gate.resolve();
|
|
261
|
+
await first;
|
|
262
|
+
const a = await hookA;
|
|
263
|
+
const b = await hookB;
|
|
264
|
+
expect(a).toMatchObject({ status: 'synced', results: [updated('ali/x')] });
|
|
265
|
+
expect(b.status).toBe('partial');
|
|
266
|
+
expect(b.results.map((r) => r.outcome)).toEqual(['conflict']);
|
|
267
|
+
// The record keeps the whole follow-up.
|
|
268
|
+
expect(svc.lastSync()?.results.map((r) => r.branch)).toEqual(['ali/x', 'juan/y']);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe('KbSyncService — a waiter keeps the branches it asked for', () => {
|
|
273
|
+
it('a caller that edits its array after queueing changes nothing about its sync or its answer', async () => {
|
|
274
|
+
const gate = deferred<void>();
|
|
275
|
+
const workflow: SyncWorkflowPort = {
|
|
276
|
+
syncWorkspaceFromRemote: vi.fn(async (id: string) => {
|
|
277
|
+
if (id === 'main') await gate.promise;
|
|
278
|
+
return updated(decodeURIComponent(id));
|
|
279
|
+
}),
|
|
280
|
+
closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
|
|
281
|
+
retireRemoteGoneClone: vi.fn(async () => true),
|
|
282
|
+
};
|
|
283
|
+
const svc = new KbSyncService(workflow, workspaces(['main', 'ali/x', 'juan/y']));
|
|
284
|
+
const first = svc.sync({ branches: ['main'] });
|
|
285
|
+
const asked = ['ali/x'];
|
|
286
|
+
const waiting = svc.sync({ branches: asked });
|
|
287
|
+
asked.push('juan/y');
|
|
288
|
+
asked[0] = 'main';
|
|
289
|
+
gate.resolve();
|
|
290
|
+
await first;
|
|
291
|
+
const r = await waiting;
|
|
292
|
+
expect(r.results.map((x) => x.branch)).toEqual(['ali/x']);
|
|
293
|
+
expect(workflow.syncWorkspaceFromRemote).not.toHaveBeenCalledWith('juan%2Fy');
|
|
294
|
+
});
|
|
295
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { createHmac } from 'node:crypto';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { verifySyncCredential, type SyncSessionVerifier } from '../sync-auth.js';
|
|
4
|
+
|
|
5
|
+
const SECRET = 'a-very-long-and-random-sync-secret';
|
|
6
|
+
|
|
7
|
+
function session(opts: { email?: string; admin?: boolean } = {}): SyncSessionVerifier {
|
|
8
|
+
return {
|
|
9
|
+
verifyJwt: vi.fn((token: string) =>
|
|
10
|
+
token === 'jwt-ok' && opts.email ? { email: opts.email } : null,
|
|
11
|
+
),
|
|
12
|
+
isAdmin: vi.fn(async () => opts.admin === true),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('verifySyncCredential', () => {
|
|
17
|
+
it('accepts the secret as a bearer', async () => {
|
|
18
|
+
const r = await verifySyncCredential(
|
|
19
|
+
{ secret: SECRET, authorization: `Bearer ${SECRET}` },
|
|
20
|
+
session(),
|
|
21
|
+
);
|
|
22
|
+
expect(r).toEqual({ ok: true, credential: { kind: 'bearer' } });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('refuses a wrong bearer with 401', async () => {
|
|
26
|
+
const r = await verifySyncCredential(
|
|
27
|
+
{ secret: SECRET, authorization: 'Bearer nope' },
|
|
28
|
+
session(),
|
|
29
|
+
);
|
|
30
|
+
expect(r).toMatchObject({ ok: false, status: 401 });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('refuses a missing credential with 401', async () => {
|
|
34
|
+
const r = await verifySyncCredential({ secret: SECRET }, session());
|
|
35
|
+
expect(r).toMatchObject({ ok: false, status: 401 });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('accepts the GitLab token header', async () => {
|
|
39
|
+
const r = await verifySyncCredential({ secret: SECRET, gitlabToken: SECRET }, session());
|
|
40
|
+
expect(r).toEqual({ ok: true, credential: { kind: 'gitlab-token' } });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('accepts a GitHub signature over the raw body, and only over those bytes', async () => {
|
|
44
|
+
const body = Buffer.from('{"ref":"refs/heads/main"}');
|
|
45
|
+
const sig = 'sha256=' + createHmac('sha256', SECRET).update(body).digest('hex');
|
|
46
|
+
expect(
|
|
47
|
+
await verifySyncCredential({ secret: SECRET, hubSignature: sig, rawBody: body }, session()),
|
|
48
|
+
).toEqual({ ok: true, credential: { kind: 'github-signature' } });
|
|
49
|
+
expect(
|
|
50
|
+
await verifySyncCredential(
|
|
51
|
+
{ secret: SECRET, hubSignature: sig, rawBody: Buffer.from('{"ref":"refs/heads/other"}') },
|
|
52
|
+
session(),
|
|
53
|
+
),
|
|
54
|
+
).toMatchObject({ ok: false, status: 401 });
|
|
55
|
+
expect(
|
|
56
|
+
await verifySyncCredential(
|
|
57
|
+
{ secret: SECRET, hubSignature: 'sha1=abc', rawBody: body },
|
|
58
|
+
session(),
|
|
59
|
+
),
|
|
60
|
+
).toMatchObject({ ok: false, status: 401 });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("accepts an admin's own session as the bearer", async () => {
|
|
64
|
+
const r = await verifySyncCredential(
|
|
65
|
+
{ secret: SECRET, authorization: 'Bearer jwt-ok' },
|
|
66
|
+
session({ email: 'admin@example.com', admin: true }),
|
|
67
|
+
);
|
|
68
|
+
expect(r).toEqual({
|
|
69
|
+
ok: true,
|
|
70
|
+
credential: { kind: 'admin-session', email: 'admin@example.com' },
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("refuses a non-admin's session", async () => {
|
|
75
|
+
const r = await verifySyncCredential(
|
|
76
|
+
{ secret: SECRET, authorization: 'Bearer jwt-ok' },
|
|
77
|
+
session({ email: 'someone@example.com', admin: false }),
|
|
78
|
+
);
|
|
79
|
+
expect(r).toMatchObject({ ok: false, status: 401 });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('with no secret configured, a header credential gets 503 that says so', async () => {
|
|
83
|
+
const r = await verifySyncCredential({ secret: '', authorization: 'Bearer anything' }, session());
|
|
84
|
+
expect(r).toMatchObject({ ok: false, status: 503 });
|
|
85
|
+
expect((r as { message: string }).message).toContain('KB_SYNC_SECRET');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('with no secret configured, an admin session still works', async () => {
|
|
89
|
+
const r = await verifySyncCredential(
|
|
90
|
+
{ secret: '', authorization: 'Bearer jwt-ok' },
|
|
91
|
+
session({ email: 'admin@example.com', admin: true }),
|
|
92
|
+
);
|
|
93
|
+
expect(r).toMatchObject({ ok: true });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('with no secret configured and no credential, 401 not 503', async () => {
|
|
97
|
+
expect(await verifySyncCredential({ secret: '' }, session())).toMatchObject({
|
|
98
|
+
ok: false,
|
|
99
|
+
status: 401,
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('verifySyncCredential — a secret set too short', () => {
|
|
105
|
+
it('is refused with 503 that says so, even when the caller presents it exactly', async () => {
|
|
106
|
+
const r = await verifySyncCredential({ secret: 'short', authorization: 'Bearer short' }, session());
|
|
107
|
+
expect(r).toMatchObject({ ok: false, status: 503 });
|
|
108
|
+
expect((r as { message: string }).message).toContain('shorter than 16');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('an admin session still works past a too-short secret', async () => {
|
|
112
|
+
const r = await verifySyncCredential(
|
|
113
|
+
{ secret: 'short', authorization: 'Bearer jwt-ok' },
|
|
114
|
+
session({ email: 'admin@example.com', admin: true }),
|
|
115
|
+
);
|
|
116
|
+
expect(r).toMatchObject({ ok: true });
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { parseSyncPayload } from '../sync-payload.js';
|
|
3
|
+
|
|
4
|
+
describe('parseSyncPayload', () => {
|
|
5
|
+
it('an empty or foreign body syncs everything', () => {
|
|
6
|
+
expect(parseSyncPayload(undefined)).toEqual({ source: 'none', branches: 'all', invalid: [] });
|
|
7
|
+
expect(parseSyncPayload({ hello: 'world' })).toEqual({ source: 'none', branches: 'all', invalid: [] });
|
|
8
|
+
expect(parseSyncPayload('text')).toEqual({ source: 'none', branches: 'all', invalid: [] });
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('explicit branches are kept in order, trimmed and de-duplicated', () => {
|
|
12
|
+
expect(parseSyncPayload({ branches: [' main ', 'ali/x', 'main'] })).toEqual({
|
|
13
|
+
source: 'explicit',
|
|
14
|
+
branches: ['main', 'ali/x'],
|
|
15
|
+
invalid: [],
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('an explicit name git would refuse is reported, not silently dropped', () => {
|
|
20
|
+
const parsed = parseSyncPayload({ branches: ['main', '--upload-pack=x', 'a..b', 7] });
|
|
21
|
+
expect(parsed.branches).toEqual(['main']);
|
|
22
|
+
expect(parsed.invalid).toEqual(['--upload-pack=x', 'a..b', '7']);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('an explicit `branches` that is not a list means everything', () => {
|
|
26
|
+
expect(parseSyncPayload({ branches: 'main' }).branches).toBe('all');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('Azure DevOps push: every updated head ref, tags ignored', () => {
|
|
30
|
+
const parsed = parseSyncPayload({
|
|
31
|
+
eventType: 'git.push',
|
|
32
|
+
resource: {
|
|
33
|
+
refUpdates: [
|
|
34
|
+
{ name: 'refs/heads/main', oldObjectId: 'a', newObjectId: 'b' },
|
|
35
|
+
{ name: 'refs/tags/v1', oldObjectId: '0', newObjectId: 'c' },
|
|
36
|
+
{ name: 'refs/heads/ali/new-skill' },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
expect(parsed).toEqual({
|
|
41
|
+
source: 'azure-devops',
|
|
42
|
+
branches: ['main', 'ali/new-skill'],
|
|
43
|
+
invalid: [],
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('Azure DevOps pull request: target then source', () => {
|
|
48
|
+
for (const eventType of ['git.pullrequest.created', 'git.pullrequest.updated', 'git.pullrequest.merged']) {
|
|
49
|
+
const parsed = parseSyncPayload({
|
|
50
|
+
eventType,
|
|
51
|
+
resource: {
|
|
52
|
+
sourceRefName: 'refs/heads/ali/new-skill',
|
|
53
|
+
targetRefName: 'refs/heads/main',
|
|
54
|
+
status: 'completed',
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
expect(parsed).toEqual({
|
|
58
|
+
source: 'azure-devops',
|
|
59
|
+
branches: ['main', 'ali/new-skill'],
|
|
60
|
+
invalid: [],
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('an Azure DevOps event we do not know syncs everything', () => {
|
|
66
|
+
expect(parseSyncPayload({ eventType: 'build.complete', resource: {} }).branches).toBe('all');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('GitHub push: the pushed branch', () => {
|
|
70
|
+
expect(parseSyncPayload({ ref: 'refs/heads/main', before: 'a', after: 'b' })).toEqual({
|
|
71
|
+
source: 'github',
|
|
72
|
+
branches: ['main'],
|
|
73
|
+
invalid: [],
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('GitHub tag push names nothing to sync', () => {
|
|
78
|
+
expect(parseSyncPayload({ ref: 'refs/tags/v1' })).toEqual({
|
|
79
|
+
source: 'github',
|
|
80
|
+
branches: [],
|
|
81
|
+
invalid: [],
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('GitLab push: the pushed branch', () => {
|
|
86
|
+
expect(parseSyncPayload({ object_kind: 'push', ref: 'refs/heads/main' })).toEqual({
|
|
87
|
+
source: 'gitlab',
|
|
88
|
+
branches: ['main'],
|
|
89
|
+
invalid: [],
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('parseSyncPayload — hostile shapes', () => {
|
|
95
|
+
it('an explicit entry that cannot be stringified is reported, not thrown', () => {
|
|
96
|
+
const parsed = parseSyncPayload({ branches: ['main', { toString: 1 }, [1], null] });
|
|
97
|
+
expect(parsed.branches).toEqual(['main']);
|
|
98
|
+
expect(parsed.invalid).toEqual(['(object)', '(array)', 'null']);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('a GitLab event that is not a push falls back to everything, even with a stray ref', () => {
|
|
102
|
+
expect(parseSyncPayload({ object_kind: 'merge_request', ref: 'refs/heads/main' })).toEqual({
|
|
103
|
+
source: 'none',
|
|
104
|
+
branches: 'all',
|
|
105
|
+
invalid: [],
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('a GitLab tag push names nothing to sync', () => {
|
|
110
|
+
expect(parseSyncPayload({ object_kind: 'tag_push', ref: 'refs/tags/v1' })).toEqual({
|
|
111
|
+
source: 'gitlab',
|
|
112
|
+
branches: [],
|
|
113
|
+
invalid: [],
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('parseSyncPayload — deletions', () => {
|
|
119
|
+
it('a GitHub branch deletion names its branch, so the sync finds it gone and retires the clone', () => {
|
|
120
|
+
expect(parseSyncPayload({ ref: 'refs/heads/ali/x', deleted: true, after: '0'.repeat(40) })).toEqual({
|
|
121
|
+
source: 'github',
|
|
122
|
+
branches: ['ali/x'],
|
|
123
|
+
invalid: [],
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('an Azure DevOps deletion (new object id all zeros) names its branch beside the real update', () => {
|
|
128
|
+
expect(
|
|
129
|
+
parseSyncPayload({
|
|
130
|
+
eventType: 'git.push',
|
|
131
|
+
resource: {
|
|
132
|
+
refUpdates: [
|
|
133
|
+
{ name: 'refs/heads/ali/x', oldObjectId: 'a'.repeat(40), newObjectId: '0'.repeat(40) },
|
|
134
|
+
{ name: 'refs/heads/main', oldObjectId: 'b'.repeat(40), newObjectId: 'c'.repeat(40) },
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
}),
|
|
138
|
+
).toEqual({ source: 'azure-devops', branches: ['ali/x', 'main'], invalid: [] });
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('a GitLab branch deletion (after all zeros) names its branch', () => {
|
|
142
|
+
expect(parseSyncPayload({ object_kind: 'push', ref: 'refs/heads/ali/x', after: '0'.repeat(40) })).toEqual({
|
|
143
|
+
source: 'gitlab',
|
|
144
|
+
branches: ['ali/x'],
|
|
145
|
+
invalid: [],
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
IKbSyncService,
|
|
3
|
+
LastSync,
|
|
4
|
+
SyncRequest,
|
|
5
|
+
SyncResult,
|
|
6
|
+
SyncWorkflowPort,
|
|
7
|
+
SyncWorkspacePort,
|
|
8
|
+
} from './kb-sync.interface.js';
|
|
9
|
+
export { KbSyncService } from './kb-sync.service.js';
|
|
10
|
+
export {
|
|
11
|
+
createKbSyncRoutes,
|
|
12
|
+
httpStatusFor,
|
|
13
|
+
isSyncRawBodyPath,
|
|
14
|
+
SYNC_RESPONSE_HEADER,
|
|
15
|
+
SYNC_RESPONSE_MARKER,
|
|
16
|
+
type KbSyncRouteDeps,
|
|
17
|
+
} from './kb-sync.routes.js';
|
|
18
|
+
export { parseSyncPayload, type ParsedSyncPayload, type SyncPayloadSource } from './sync-payload.js';
|
|
19
|
+
export { verifySyncCredential, type SyncAuthInput, type SyncAuthResult, type SyncCredential } from './sync-auth.js';
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { BranchSyncOutcome } from '@bevel-software/platform-shared';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Remote sync: a git host (Azure DevOps, GitHub, GitLab, a pipeline) tells
|
|
5
|
+
* Hexis "the repository changed" and Hexis brings the clones it holds up to
|
|
6
|
+
* date. The module owns the HTTP surface, the credential check, the payload
|
|
7
|
+
* parsing and the coalescing; every git operation happens behind the
|
|
8
|
+
* workflow port below, never here.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Which branches a caller asked to sync. `'all'` = every clone Hexis holds. */
|
|
12
|
+
export interface SyncRequest {
|
|
13
|
+
branches: string[] | 'all';
|
|
14
|
+
/** Who asked — a credential kind or an admin's email — for the last-sync record. */
|
|
15
|
+
by?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* What the most recent sync did, for the admin's Deployment page. Kept in
|
|
20
|
+
* memory only: it answers "is the hook wired up and working?", and a restart
|
|
21
|
+
* resetting it to "none yet" is the honest answer for a fresh process.
|
|
22
|
+
*/
|
|
23
|
+
export interface LastSync {
|
|
24
|
+
/** Epoch ms when the sync finished. */
|
|
25
|
+
at: number;
|
|
26
|
+
by: string;
|
|
27
|
+
status: SyncResult['status'];
|
|
28
|
+
/** One entry per branch, as the response carried them. */
|
|
29
|
+
results: BranchSyncOutcome[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface SyncResult {
|
|
33
|
+
/** `synced` when no branch needs a person or a retry; `partial` otherwise. */
|
|
34
|
+
status: 'synced' | 'partial';
|
|
35
|
+
results: BranchSyncOutcome[];
|
|
36
|
+
changeRequests: {
|
|
37
|
+
/** Open change requests closed because one of their branches is gone from the host. */
|
|
38
|
+
closedDeletedBranch: number;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface IKbSyncService {
|
|
43
|
+
/**
|
|
44
|
+
* Sync the requested branches. One sync runs at a time; a request arriving
|
|
45
|
+
* mid-run is folded into a single queued follow-up (the union of every
|
|
46
|
+
* request that arrived meanwhile) and resolves with that follow-up's result.
|
|
47
|
+
* Never rejects for a per-branch failure — those are outcomes.
|
|
48
|
+
*/
|
|
49
|
+
sync(request: SyncRequest): Promise<SyncResult>;
|
|
50
|
+
/** The most recent completed sync in this process, or null before the first. */
|
|
51
|
+
lastSync(): LastSync | null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The slice of the workflow module a sync drives. */
|
|
55
|
+
export interface SyncWorkflowPort {
|
|
56
|
+
syncWorkspaceFromRemote(workspaceId: string): Promise<BranchSyncOutcome>;
|
|
57
|
+
/**
|
|
58
|
+
* Retire the clone of a branch the host deleted, under the branch-lifecycle
|
|
59
|
+
* lock and only if origin still lacks it. Returns whether it was removed.
|
|
60
|
+
*/
|
|
61
|
+
retireRemoteGoneClone(workspaceId: string): Promise<boolean>;
|
|
62
|
+
/** The existing sweep; returns how many requests it closed. */
|
|
63
|
+
closeChangeRequestsWithDeletedBranches(): Promise<number>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The slice of the workspace module a sync reads: which clones exist ON
|
|
68
|
+
* DISK. Not the in-memory "seen this process" cache — clones survive a
|
|
69
|
+
* restart and a hook that fires before anyone has opened a branch must still
|
|
70
|
+
* find them.
|
|
71
|
+
*/
|
|
72
|
+
export interface SyncWorkspacePort {
|
|
73
|
+
/**
|
|
74
|
+
* `unreadable` names a clone the listing could see but not probe (an ACL,
|
|
75
|
+
* a half-deleted directory); the sync reports it as that branch's error
|
|
76
|
+
* rather than letting one bad directory fail every other branch.
|
|
77
|
+
*/
|
|
78
|
+
listClonedWorkspaces(): Promise<Array<{ id: string; branch: string; unreadable?: string }>>;
|
|
79
|
+
}
|