@bevel-software/platform-core-backend 0.14.0 → 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 +99 -8
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +18 -1
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +96 -43
- package/dist/core/create-core-services.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/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/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/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/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 -92
- package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +6 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +18 -25
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +40 -2
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +227 -54
- 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.service.d.ts +52 -1
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +242 -9
- 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 +9 -9
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +33 -0
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +110 -16
- 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/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__/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 +123 -7
- package/src/core/create-core-services.ts +162 -41
- 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/plugin-principals.ts +137 -0
- package/src/modules/database/core-schema.ts +54 -0
- 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/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 +24 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.cli.test.ts +2 -0
- package/src/modules/tool-manuals/mcp-json-discovery.ts +154 -88
- package/src/modules/tool-manuals/tool-manuals.service.ts +14 -23
- package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +1 -1
- package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +1 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +2 -2
- 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.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 +239 -56
- package/src/modules/workflow/git/pull-request.service.ts +9 -0
- package/src/modules/workflow/workflow.service.ts +260 -9
- 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 +9 -17
- package/src/modules/workspace/workspace.service.ts +107 -16
- 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__/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/ttl-cache.ts +33 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import type { IAdminAccessService } from '../admin/admin.interface.js';
|
|
3
|
+
import type { IKbSyncService, SyncResult } from './kb-sync.interface.js';
|
|
4
|
+
/**
|
|
5
|
+
* Remote sync — a git host or a pipeline tells Hexis the repository changed.
|
|
6
|
+
*
|
|
7
|
+
* POST /api/sync/<branch> sync that one branch — the branch whose change
|
|
8
|
+
* made the call. What a GitHub Action or a
|
|
9
|
+
* pipeline step uses: the branch name goes in the
|
|
10
|
+
* URL, the body is ignored for branch selection.
|
|
11
|
+
* POST /api/sync the branches named by the body — an Azure
|
|
12
|
+
* DevOps service hook or a GitHub/GitLab webhook
|
|
13
|
+
* payload (they cannot template the URL), an
|
|
14
|
+
* explicit `{ "branches": [...] }`, or nothing,
|
|
15
|
+
* which syncs every clone Hexis holds.
|
|
16
|
+
*
|
|
17
|
+
* Mounted BEFORE every JWT-protected `/api` mount: the caller is a machine
|
|
18
|
+
* holding the deployment's sync secret, not a browser session, and Express 5
|
|
19
|
+
* would otherwise 401 it in the first protected mount. The route parses its
|
|
20
|
+
* own body as raw bytes (the global JSON parser skips this path) because the
|
|
21
|
+
* GitHub-style credential is an HMAC over exactly the bytes that arrived.
|
|
22
|
+
*
|
|
23
|
+
* Status codes are chosen for the two kinds of caller. A pipeline's
|
|
24
|
+
* `curl --fail` must fail exactly when Hexis is not in sync; a webhook host
|
|
25
|
+
* retries 5xx and gives up on 4xx:
|
|
26
|
+
*
|
|
27
|
+
* 200 every branch updated, already current, or not cloned here
|
|
28
|
+
* 409 a branch has a CONFLICT: Hexis-side commits clash with the host.
|
|
29
|
+
* Automatic recovery is queued, but the branch is not in sync at the
|
|
30
|
+
* time of the answer; the body's `error` names the files. A retry
|
|
31
|
+
* answers 200 once recovery (or a person) has cleared it.
|
|
32
|
+
* 503 a branch could not be pulled (origin unreachable, credential
|
|
33
|
+
* refused) — retriable. Also: no sync secret configured.
|
|
34
|
+
* 401 missing or wrong credential
|
|
35
|
+
* 400 a branch name git would refuse, or a body that is not JSON
|
|
36
|
+
*/
|
|
37
|
+
export interface KbSyncRouteDeps {
|
|
38
|
+
kbSync: IKbSyncService;
|
|
39
|
+
/** The configured secret, read per request so a setup-screen save applies at once. */
|
|
40
|
+
syncSecret: () => string;
|
|
41
|
+
authService: {
|
|
42
|
+
verifyToken(token: string): {
|
|
43
|
+
userId: string;
|
|
44
|
+
email: string;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
adminAccess: IAdminAccessService;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Stamped on EVERY response this router writes. The endpoint answers 503 for
|
|
51
|
+
* an ordinary result (a branch could not be pulled), and a reverse proxy
|
|
52
|
+
* answers 503 when the backend is down; by status alone the browser cannot
|
|
53
|
+
* tell them apart, and it must — one is a line on the page, the other the
|
|
54
|
+
* maintenance overlay. A proxy standing in for a dead upstream never sets
|
|
55
|
+
* this header. Exposed to cross-origin callers in `create-core-server.ts`.
|
|
56
|
+
*/
|
|
57
|
+
export declare const SYNC_RESPONSE_HEADER = "X-Hexis-Sync";
|
|
58
|
+
export declare const SYNC_RESPONSE_MARKER = "result";
|
|
59
|
+
/**
|
|
60
|
+
* Whether a request path belongs to this router and must reach it with its
|
|
61
|
+
* body UNPARSED. The server's global JSON parser consults this: once that
|
|
62
|
+
* parser has read the stream, `express.raw` below sees nothing and the
|
|
63
|
+
* signature credential has no bytes to verify. Both routes share the check,
|
|
64
|
+
* so the branch form (`/api/sync/<branch>`) is covered as well as the bare
|
|
65
|
+
* one — an exact-path exemption used to protect only the latter.
|
|
66
|
+
*/
|
|
67
|
+
export declare function isSyncRawBodyPath(path: string): boolean;
|
|
68
|
+
export declare function httpStatusFor(result: SyncResult): 200 | 409 | 503;
|
|
69
|
+
export declare function createKbSyncRoutes(deps: KbSyncRouteDeps): express.Router;
|
|
70
|
+
//# sourceMappingURL=kb-sync.routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kb-sync.routes.d.ts","sourceRoot":"","sources":["../../../src/modules/kb-sync/kb-sync.routes.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAEvE,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAIzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,cAAc,CAAC;IACvB,sFAAsF;IACtF,UAAU,EAAE,MAAM,MAAM,CAAC;IACzB,WAAW,EAAE;QAAE,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;IAC/E,WAAW,EAAE,mBAAmB,CAAC;CAClC;AAKD;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,iBAAiB,CAAC;AACnD,eAAO,MAAM,oBAAoB,WAAW,CAAC;AAE7C;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CASvD;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAIjE;AAID,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAsGxE"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { assertValidBranchName } from '../kb-fs/branch-name.js';
|
|
3
|
+
import { parseSyncPayload } from './sync-payload.js';
|
|
4
|
+
import { verifySyncCredential } from './sync-auth.js';
|
|
5
|
+
/** Cap on a webhook body. ADO's PR payloads run to a few hundred KB. */
|
|
6
|
+
const BODY_LIMIT = '2mb';
|
|
7
|
+
/**
|
|
8
|
+
* Stamped on EVERY response this router writes. The endpoint answers 503 for
|
|
9
|
+
* an ordinary result (a branch could not be pulled), and a reverse proxy
|
|
10
|
+
* answers 503 when the backend is down; by status alone the browser cannot
|
|
11
|
+
* tell them apart, and it must — one is a line on the page, the other the
|
|
12
|
+
* maintenance overlay. A proxy standing in for a dead upstream never sets
|
|
13
|
+
* this header. Exposed to cross-origin callers in `create-core-server.ts`.
|
|
14
|
+
*/
|
|
15
|
+
export const SYNC_RESPONSE_HEADER = 'X-Hexis-Sync';
|
|
16
|
+
export const SYNC_RESPONSE_MARKER = 'result';
|
|
17
|
+
/**
|
|
18
|
+
* Whether a request path belongs to this router and must reach it with its
|
|
19
|
+
* body UNPARSED. The server's global JSON parser consults this: once that
|
|
20
|
+
* parser has read the stream, `express.raw` below sees nothing and the
|
|
21
|
+
* signature credential has no bytes to verify. Both routes share the check,
|
|
22
|
+
* so the branch form (`/api/sync/<branch>`) is covered as well as the bare
|
|
23
|
+
* one — an exact-path exemption used to protect only the latter.
|
|
24
|
+
*/
|
|
25
|
+
export function isSyncRawBodyPath(path) {
|
|
26
|
+
// Case-insensitive, because Express routing is: `/api/Sync` reaches this
|
|
27
|
+
// router, and if this check said no the global parser would eat the body
|
|
28
|
+
// first. On either form a signed call would then 401 (no bytes to verify).
|
|
29
|
+
// On the bare form a bearer call would silently sync every clone, since a
|
|
30
|
+
// body that is no longer a Buffer parses as "no body"; the branch form is
|
|
31
|
+
// unaffected there, its branch comes from the URL.
|
|
32
|
+
const lower = path.toLowerCase();
|
|
33
|
+
return lower === '/api/sync' || lower.startsWith('/api/sync/');
|
|
34
|
+
}
|
|
35
|
+
export function httpStatusFor(result) {
|
|
36
|
+
if (result.results.some((r) => r.outcome === 'conflict'))
|
|
37
|
+
return 409;
|
|
38
|
+
if (result.results.some((r) => r.outcome === 'error'))
|
|
39
|
+
return 503;
|
|
40
|
+
return 200;
|
|
41
|
+
}
|
|
42
|
+
export function createKbSyncRoutes(deps) {
|
|
43
|
+
const router = express.Router();
|
|
44
|
+
const rawBody = express.raw({ type: () => true, limit: BODY_LIMIT });
|
|
45
|
+
/**
|
|
46
|
+
* Everything both routes share: the credential check, then the sync and
|
|
47
|
+
* its answer. `select` turns the request into the branches to sync, or
|
|
48
|
+
* sends its own 400 and returns null.
|
|
49
|
+
*/
|
|
50
|
+
async function handle(req, res, select) {
|
|
51
|
+
res.setHeader(SYNC_RESPONSE_HEADER, SYNC_RESPONSE_MARKER);
|
|
52
|
+
const raw = Buffer.isBuffer(req.body) ? req.body : undefined;
|
|
53
|
+
const auth = await verifySyncCredential({
|
|
54
|
+
secret: deps.syncSecret(),
|
|
55
|
+
authorization: header(req, 'authorization'),
|
|
56
|
+
gitlabToken: header(req, 'x-gitlab-token'),
|
|
57
|
+
hubSignature: header(req, 'x-hub-signature-256'),
|
|
58
|
+
rawBody: raw,
|
|
59
|
+
}, {
|
|
60
|
+
verifyJwt: (token) => {
|
|
61
|
+
try {
|
|
62
|
+
return { email: deps.authService.verifyToken(token).email };
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
isAdmin: (email) => deps.adminAccess.isAdmin(email),
|
|
69
|
+
});
|
|
70
|
+
if (!auth.ok) {
|
|
71
|
+
if (auth.status === 401)
|
|
72
|
+
res.setHeader('WWW-Authenticate', 'Bearer realm="hexis-sync"');
|
|
73
|
+
res.status(auth.status).json({ error: auth.message });
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const selection = select();
|
|
77
|
+
if (!selection)
|
|
78
|
+
return;
|
|
79
|
+
const who = auth.credential.kind === 'admin-session' ? auth.credential.email : auth.credential.kind;
|
|
80
|
+
const asked = selection.branches === 'all' ? 'all cloned branches' : selection.branches.join(', ') || '(none)';
|
|
81
|
+
try {
|
|
82
|
+
const result = await deps.kbSync.sync({ branches: selection.branches, by: who });
|
|
83
|
+
const summary = result.results.map((r) => `${r.branch}=${r.outcome}`).join(' ');
|
|
84
|
+
console.log(`[sync] by ${who} via ${selection.source} for ${asked}: ${result.status} ${summary}`);
|
|
85
|
+
res.status(httpStatusFor(result)).json(result);
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
console.error('[sync] unhandled error:', err);
|
|
89
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// The branch in the URL. A wildcard rather than `:branch` so a branch with
|
|
93
|
+
// slashes (`ali/new-skill`) works spelled either way — as segments or
|
|
94
|
+
// percent-encoded as one.
|
|
95
|
+
router.post('/sync/*branch', rawBody, (req, res) => handle(req, res, () => {
|
|
96
|
+
const param = req.params.branch;
|
|
97
|
+
const branch = (Array.isArray(param) ? param.join('/') : String(param ?? '')).trim();
|
|
98
|
+
try {
|
|
99
|
+
assertValidBranchName(branch);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
res.status(400).json({ error: `Not a valid branch name: ${branch}` });
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
return { source: 'path', branches: [branch] };
|
|
106
|
+
}));
|
|
107
|
+
router.post('/sync', rawBody, (req, res) => handle(req, res, () => {
|
|
108
|
+
const raw = Buffer.isBuffer(req.body) ? req.body : undefined;
|
|
109
|
+
let body = undefined;
|
|
110
|
+
if (raw && raw.length > 0) {
|
|
111
|
+
try {
|
|
112
|
+
body = JSON.parse(raw.toString('utf8'));
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
res.status(400).json({ error: 'The request body is not valid JSON.' });
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const parsed = parseSyncPayload(body);
|
|
120
|
+
if (parsed.invalid.length > 0) {
|
|
121
|
+
res.status(400).json({
|
|
122
|
+
error: `Not a valid branch name: ${parsed.invalid.join(', ')}`,
|
|
123
|
+
invalid: parsed.invalid,
|
|
124
|
+
});
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
return { source: parsed.source, branches: parsed.branches };
|
|
128
|
+
}));
|
|
129
|
+
return router;
|
|
130
|
+
}
|
|
131
|
+
function header(req, name) {
|
|
132
|
+
const v = req.headers[name];
|
|
133
|
+
return typeof v === 'string' ? v : undefined;
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=kb-sync.routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kb-sync.routes.js","sourceRoot":"","sources":["../../../src/modules/kb-sync/kb-sync.routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { IKbSyncService, LastSync, SyncRequest, SyncResult, SyncWorkflowPort, SyncWorkspacePort } from './kb-sync.interface.js';
|
|
2
|
+
/**
|
|
3
|
+
* Runs remote syncs one at a time and folds everything that arrives
|
|
4
|
+
* mid-run into ONE follow-up.
|
|
5
|
+
*
|
|
6
|
+
* Why coalesce: a git host fires several hooks for one event — Azure DevOps
|
|
7
|
+
* sends a push, a PR-updated and a PR-merged within a second of each other —
|
|
8
|
+
* and each would otherwise queue behind the workspace mutex to pull the same
|
|
9
|
+
* tree again. The follow-up pulls the union of every request that arrived
|
|
10
|
+
* during the run (an "all" absorbs the rest).
|
|
11
|
+
*
|
|
12
|
+
* But each caller is answered for the branches IT asked about. The endpoint
|
|
13
|
+
* promises to fail exactly when the caller's branch is not in sync; a hook
|
|
14
|
+
* for branch A answered with branch B's conflict would fail the wrong
|
|
15
|
+
* subscription — and a host that disables subscriptions after repeated
|
|
16
|
+
* failures would disable the wrong one.
|
|
17
|
+
*
|
|
18
|
+
* Branches are pulled sequentially. N is small (only cloned branches count),
|
|
19
|
+
* the per-clone mutex serialises them anyway, and one slow origin round-trip
|
|
20
|
+
* at a time keeps the process calm during a burst.
|
|
21
|
+
*/
|
|
22
|
+
export declare class KbSyncService implements IKbSyncService {
|
|
23
|
+
private readonly workflow;
|
|
24
|
+
private readonly workspaces;
|
|
25
|
+
private readonly now;
|
|
26
|
+
private inFlight;
|
|
27
|
+
/** Everyone who arrived mid-run, each with what they asked for. */
|
|
28
|
+
private waiters;
|
|
29
|
+
private last;
|
|
30
|
+
constructor(workflow: SyncWorkflowPort, workspaces: SyncWorkspacePort, now?: () => number);
|
|
31
|
+
lastSync(): LastSync | null;
|
|
32
|
+
sync(request: SyncRequest): Promise<SyncResult>;
|
|
33
|
+
private start;
|
|
34
|
+
/**
|
|
35
|
+
* Run ONE follow-up for everyone who arrived during the last run: the union
|
|
36
|
+
* of their branches, then each waiter answered from its own share of the
|
|
37
|
+
* result — never from another caller's branches.
|
|
38
|
+
*/
|
|
39
|
+
private drain;
|
|
40
|
+
/** The part of a union run that answers one caller: its branches, and its status over them. */
|
|
41
|
+
private static subsetFor;
|
|
42
|
+
private static statusOf;
|
|
43
|
+
private run;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=kb-sync.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kb-sync.service.d.ts","sourceRoot":"","sources":["../../../src/modules/kb-sync/kb-sync.service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,cAAc,EACd,QAAQ,EACR,WAAW,EACX,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,wBAAwB,CAAC;AAEhC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,aAAc,YAAW,cAAc;IAYhD,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,GAAG;IAbtB,OAAO,CAAC,QAAQ,CAAiC;IACjD,mEAAmE;IACnE,OAAO,CAAC,OAAO,CAKP;IACR,OAAO,CAAC,IAAI,CAAyB;gBAGlB,QAAQ,EAAE,gBAAgB,EAC1B,UAAU,EAAE,iBAAiB,EAC7B,GAAG,GAAE,MAAM,MAAyB;IAGvD,QAAQ,IAAI,QAAQ,GAAG,IAAI;IAI3B,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAW/C,OAAO,CAAC,KAAK;IASb;;;;OAIG;IACH,OAAO,CAAC,KAAK;IA2Bb,+FAA+F;IAC/F,OAAO,CAAC,MAAM,CAAC,SAAS;IAWxB,OAAO,CAAC,MAAM,CAAC,QAAQ;YAWT,GAAG;CAyElB"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { sanitizeError } from '../workflow/sanitize-error.js';
|
|
2
|
+
/**
|
|
3
|
+
* Runs remote syncs one at a time and folds everything that arrives
|
|
4
|
+
* mid-run into ONE follow-up.
|
|
5
|
+
*
|
|
6
|
+
* Why coalesce: a git host fires several hooks for one event — Azure DevOps
|
|
7
|
+
* sends a push, a PR-updated and a PR-merged within a second of each other —
|
|
8
|
+
* and each would otherwise queue behind the workspace mutex to pull the same
|
|
9
|
+
* tree again. The follow-up pulls the union of every request that arrived
|
|
10
|
+
* during the run (an "all" absorbs the rest).
|
|
11
|
+
*
|
|
12
|
+
* But each caller is answered for the branches IT asked about. The endpoint
|
|
13
|
+
* promises to fail exactly when the caller's branch is not in sync; a hook
|
|
14
|
+
* for branch A answered with branch B's conflict would fail the wrong
|
|
15
|
+
* subscription — and a host that disables subscriptions after repeated
|
|
16
|
+
* failures would disable the wrong one.
|
|
17
|
+
*
|
|
18
|
+
* Branches are pulled sequentially. N is small (only cloned branches count),
|
|
19
|
+
* the per-clone mutex serialises them anyway, and one slow origin round-trip
|
|
20
|
+
* at a time keeps the process calm during a burst.
|
|
21
|
+
*/
|
|
22
|
+
export class KbSyncService {
|
|
23
|
+
workflow;
|
|
24
|
+
workspaces;
|
|
25
|
+
now;
|
|
26
|
+
inFlight = null;
|
|
27
|
+
/** Everyone who arrived mid-run, each with what they asked for. */
|
|
28
|
+
waiters = [];
|
|
29
|
+
last = null;
|
|
30
|
+
constructor(workflow, workspaces, now = () => Date.now()) {
|
|
31
|
+
this.workflow = workflow;
|
|
32
|
+
this.workspaces = workspaces;
|
|
33
|
+
this.now = now;
|
|
34
|
+
}
|
|
35
|
+
lastSync() {
|
|
36
|
+
return this.last;
|
|
37
|
+
}
|
|
38
|
+
sync(request) {
|
|
39
|
+
if (!this.inFlight)
|
|
40
|
+
return this.start(request);
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
// Copied on entry: the caller keeps its array, and a caller that reuses
|
|
43
|
+
// or edits it before the follow-up runs must not change what is pulled
|
|
44
|
+
// or what it is answered for.
|
|
45
|
+
const branches = request.branches === 'all' ? 'all' : [...request.branches];
|
|
46
|
+
this.waiters.push({ branches, by: request.by, resolve, reject });
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
start(request) {
|
|
50
|
+
const run = this.run(request);
|
|
51
|
+
this.inFlight = run.catch(() => undefined).finally(() => {
|
|
52
|
+
this.inFlight = null;
|
|
53
|
+
this.drain();
|
|
54
|
+
});
|
|
55
|
+
return run;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Run ONE follow-up for everyone who arrived during the last run: the union
|
|
59
|
+
* of their branches, then each waiter answered from its own share of the
|
|
60
|
+
* result — never from another caller's branches.
|
|
61
|
+
*/
|
|
62
|
+
drain() {
|
|
63
|
+
if (this.waiters.length === 0)
|
|
64
|
+
return;
|
|
65
|
+
const waiters = this.waiters;
|
|
66
|
+
this.waiters = [];
|
|
67
|
+
const union = new Set();
|
|
68
|
+
let all = false;
|
|
69
|
+
const by = new Set();
|
|
70
|
+
for (const w of waiters) {
|
|
71
|
+
if (w.branches === 'all')
|
|
72
|
+
all = true;
|
|
73
|
+
else
|
|
74
|
+
for (const b of w.branches)
|
|
75
|
+
union.add(b);
|
|
76
|
+
if (w.by)
|
|
77
|
+
by.add(w.by);
|
|
78
|
+
}
|
|
79
|
+
const request = {
|
|
80
|
+
branches: all ? 'all' : [...union],
|
|
81
|
+
by: by.size > 0 ? [...by].join(', ') : undefined,
|
|
82
|
+
};
|
|
83
|
+
this.start(request).then((result) => {
|
|
84
|
+
for (const w of waiters)
|
|
85
|
+
w.resolve(KbSyncService.subsetFor(result, w.branches));
|
|
86
|
+
}, (err) => {
|
|
87
|
+
for (const w of waiters)
|
|
88
|
+
w.reject(err);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/** The part of a union run that answers one caller: its branches, and its status over them. */
|
|
92
|
+
static subsetFor(result, branches) {
|
|
93
|
+
if (branches === 'all')
|
|
94
|
+
return result;
|
|
95
|
+
const wanted = new Set(branches);
|
|
96
|
+
const results = result.results.filter((r) => wanted.has(r.branch));
|
|
97
|
+
return {
|
|
98
|
+
status: KbSyncService.statusOf(results),
|
|
99
|
+
results,
|
|
100
|
+
changeRequests: result.changeRequests,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
static statusOf(results) {
|
|
104
|
+
const clean = results.every((r) => r.outcome === 'updated' ||
|
|
105
|
+
r.outcome === 'up-to-date' ||
|
|
106
|
+
r.outcome === 'not-cloned' ||
|
|
107
|
+
r.outcome === 'remote-gone');
|
|
108
|
+
return clean ? 'synced' : 'partial';
|
|
109
|
+
}
|
|
110
|
+
async run(request) {
|
|
111
|
+
const known = await this.workspaces.listClonedWorkspaces();
|
|
112
|
+
const byBranch = new Map(known.map((w) => [w.branch, w.id]));
|
|
113
|
+
const targets = request.branches === 'all'
|
|
114
|
+
? known.map((w) => ({ branch: w.branch, id: w.id }))
|
|
115
|
+
: request.branches.map((branch) => ({ branch, id: byBranch.get(branch) ?? null }));
|
|
116
|
+
const unreadable = new Map(known.filter((w) => w.unreadable).map((w) => [w.branch, w.unreadable]));
|
|
117
|
+
const results = [];
|
|
118
|
+
for (const { branch, id } of targets) {
|
|
119
|
+
if (id === null) {
|
|
120
|
+
results.push({ branch, outcome: 'not-cloned' });
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const cannotRead = unreadable.get(branch);
|
|
124
|
+
if (cannotRead) {
|
|
125
|
+
results.push({ branch, outcome: 'error', error: cannotRead });
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
let outcome;
|
|
129
|
+
try {
|
|
130
|
+
outcome = await this.workflow.syncWorkspaceFromRemote(id);
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
// The workflow step promises outcomes, not throws — but one broken
|
|
134
|
+
// clone must never take the other branches, the sweep, or every
|
|
135
|
+
// coalesced caller down with it, so the promise is enforced here too.
|
|
136
|
+
const message = sanitizeError(err);
|
|
137
|
+
console.error(`[sync] unexpected failure syncing "${branch}": ${message}`);
|
|
138
|
+
outcome = { branch, outcome: 'error', error: message };
|
|
139
|
+
}
|
|
140
|
+
results.push(outcome);
|
|
141
|
+
if (outcome.outcome === 'remote-gone') {
|
|
142
|
+
// The host deleted the branch. Left in place, the stale clone would
|
|
143
|
+
// fail its fetch on every following full sync — and a webhook host
|
|
144
|
+
// disables a subscription that keeps failing. The workflow retires it
|
|
145
|
+
// under the branch-lifecycle lock, after asking origin again, so a
|
|
146
|
+
// branch recreated since the pull keeps its clone. Best-effort.
|
|
147
|
+
try {
|
|
148
|
+
const removed = await this.workflow.retireRemoteGoneClone(id);
|
|
149
|
+
if (removed)
|
|
150
|
+
console.log(`[sync] removed the clone of "${branch}" — deleted on the host`);
|
|
151
|
+
else
|
|
152
|
+
console.log(`[sync] kept the clone of "${branch}" — it is back on the host, or already gone`);
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
console.warn(`[sync] could not remove the clone of "${branch}":`, sanitizeError(err));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// The host may have deleted a branch (an ADO PR completed with "delete
|
|
160
|
+
// source branch"); the existing sweep closes the Hexis request that
|
|
161
|
+
// pointed at it. Best-effort — it fails safe by closing nothing.
|
|
162
|
+
let closedDeletedBranch = 0;
|
|
163
|
+
try {
|
|
164
|
+
closedDeletedBranch = await this.workflow.closeChangeRequestsWithDeletedBranches();
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
console.warn('[sync] deleted-branch sweep failed:', err instanceof Error ? err.message : err);
|
|
168
|
+
}
|
|
169
|
+
const result = {
|
|
170
|
+
status: KbSyncService.statusOf(results),
|
|
171
|
+
results,
|
|
172
|
+
changeRequests: { closedDeletedBranch },
|
|
173
|
+
};
|
|
174
|
+
this.last = {
|
|
175
|
+
at: this.now(),
|
|
176
|
+
by: request.by ?? 'unknown',
|
|
177
|
+
status: result.status,
|
|
178
|
+
results: [...results],
|
|
179
|
+
};
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=kb-sync.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kb-sync.service.js","sourceRoot":"","sources":["../../../src/modules/kb-sync/kb-sync.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who may call `POST /api/sync`, decided from the request's credentials.
|
|
3
|
+
*
|
|
4
|
+
* Framework-agnostic on purpose (the route hands in plain strings and the
|
|
5
|
+
* raw body) so the rule is one function with one test, not something spread
|
|
6
|
+
* across middleware.
|
|
7
|
+
*
|
|
8
|
+
* Four credentials are accepted, because the callers cannot all speak the
|
|
9
|
+
* same one:
|
|
10
|
+
*
|
|
11
|
+
* bearer `Authorization: Bearer <secret>` — ADO Web Hooks and
|
|
12
|
+
* any pipeline can set a header.
|
|
13
|
+
* gitlab-token `X-Gitlab-Token: <secret>` — what GitLab sends.
|
|
14
|
+
* github-signature `X-Hub-Signature-256: sha256=<hmac of the raw body>` —
|
|
15
|
+
* GitHub and Gitea cannot set headers, only sign.
|
|
16
|
+
* admin-session a browser JWT whose user is an admin, so a "Sync now"
|
|
17
|
+
* button and a curl with your own session both work while
|
|
18
|
+
* the hook is being wired up.
|
|
19
|
+
*
|
|
20
|
+
* Every comparison is constant-time. The secret never travels in the URL.
|
|
21
|
+
*/
|
|
22
|
+
export type SyncCredential = {
|
|
23
|
+
kind: 'bearer' | 'gitlab-token' | 'github-signature';
|
|
24
|
+
} | {
|
|
25
|
+
kind: 'admin-session';
|
|
26
|
+
email: string;
|
|
27
|
+
};
|
|
28
|
+
export type SyncAuthResult = {
|
|
29
|
+
ok: true;
|
|
30
|
+
credential: SyncCredential;
|
|
31
|
+
} | {
|
|
32
|
+
ok: false;
|
|
33
|
+
status: 401 | 503;
|
|
34
|
+
message: string;
|
|
35
|
+
};
|
|
36
|
+
export interface SyncAuthInput {
|
|
37
|
+
/** The configured secret, or empty when none is set. */
|
|
38
|
+
secret: string;
|
|
39
|
+
authorization?: string;
|
|
40
|
+
gitlabToken?: string;
|
|
41
|
+
hubSignature?: string;
|
|
42
|
+
/** The request body exactly as received — the HMAC is over these bytes. */
|
|
43
|
+
rawBody?: Buffer;
|
|
44
|
+
}
|
|
45
|
+
export interface SyncSessionVerifier {
|
|
46
|
+
/** Resolve a browser JWT to its email, or null when it is not one. */
|
|
47
|
+
verifyJwt(token: string): {
|
|
48
|
+
email: string;
|
|
49
|
+
} | null;
|
|
50
|
+
isAdmin(email: string): Promise<boolean>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Shortest secret the endpoint will honour. The setup screen enforces the
|
|
54
|
+
* same floor on save; this is the boundary that also covers a secret set
|
|
55
|
+
* through the environment, which no validator ever sees.
|
|
56
|
+
*/
|
|
57
|
+
export declare const MIN_SYNC_SECRET_LENGTH = 16;
|
|
58
|
+
export declare function verifySyncCredential(input: SyncAuthInput, session: SyncSessionVerifier): Promise<SyncAuthResult>;
|
|
59
|
+
//# sourceMappingURL=sync-auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-auth.d.ts","sourceRoot":"","sources":["../../../src/modules/kb-sync/sync-auth.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,QAAQ,GAAG,cAAc,GAAG,kBAAkB,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7C,MAAM,MAAM,cAAc,GACtB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,cAAc,CAAA;CAAE,GACxC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,GAAG,GAAG,GAAG,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtD,MAAM,WAAW,aAAa;IAC5B,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,sEAAsE;IACtE,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACnD,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1C;AAMD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAazC,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,cAAc,CAAC,CA4CzB"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { createHmac } from 'node:crypto';
|
|
2
|
+
import { timingSafeStringEqual } from '../auth/password-hash.js';
|
|
3
|
+
const NO_SECRET = 'No sync secret is configured. Set KB_SYNC_SECRET (or the setup screen’s sync secret), ' +
|
|
4
|
+
'or call this endpoint with an administrator session.';
|
|
5
|
+
/**
|
|
6
|
+
* Shortest secret the endpoint will honour. The setup screen enforces the
|
|
7
|
+
* same floor on save; this is the boundary that also covers a secret set
|
|
8
|
+
* through the environment, which no validator ever sees.
|
|
9
|
+
*/
|
|
10
|
+
export const MIN_SYNC_SECRET_LENGTH = 16;
|
|
11
|
+
const SECRET_TOO_SHORT = `The configured sync secret is shorter than ${MIN_SYNC_SECRET_LENGTH} characters and is not ` +
|
|
12
|
+
'accepted. Set a longer KB_SYNC_SECRET, or call this endpoint with an administrator session.';
|
|
13
|
+
function signatureMatches(secret, rawBody, header) {
|
|
14
|
+
const [scheme, hex] = header.split('=', 2);
|
|
15
|
+
if (scheme !== 'sha256' || !hex)
|
|
16
|
+
return false;
|
|
17
|
+
const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
|
|
18
|
+
return timingSafeStringEqual(expected, hex.toLowerCase());
|
|
19
|
+
}
|
|
20
|
+
export async function verifySyncCredential(input, session) {
|
|
21
|
+
const configured = input.secret.trim();
|
|
22
|
+
// A too-short secret is treated as absent for matching purposes and named
|
|
23
|
+
// as the problem below, so a one-character deployment secret can never be
|
|
24
|
+
// what lets a caller in.
|
|
25
|
+
const usable = configured.length >= MIN_SYNC_SECRET_LENGTH;
|
|
26
|
+
const secret = usable ? configured : '';
|
|
27
|
+
const bearer = input.authorization?.startsWith('Bearer ')
|
|
28
|
+
? input.authorization.slice('Bearer '.length).trim()
|
|
29
|
+
: '';
|
|
30
|
+
// A shared secret presented in any of its three shapes.
|
|
31
|
+
if (secret) {
|
|
32
|
+
if (bearer && timingSafeStringEqual(bearer, secret)) {
|
|
33
|
+
return { ok: true, credential: { kind: 'bearer' } };
|
|
34
|
+
}
|
|
35
|
+
if (input.gitlabToken && timingSafeStringEqual(input.gitlabToken.trim(), secret)) {
|
|
36
|
+
return { ok: true, credential: { kind: 'gitlab-token' } };
|
|
37
|
+
}
|
|
38
|
+
if (input.hubSignature && input.rawBody && signatureMatches(secret, input.rawBody, input.hubSignature.trim())) {
|
|
39
|
+
return { ok: true, credential: { kind: 'github-signature' } };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// An admin's own session, carried as the bearer. Checked after the secret
|
|
43
|
+
// so a secret that happens to parse as a JWT is never mistaken for one.
|
|
44
|
+
if (bearer) {
|
|
45
|
+
const claim = session.verifyJwt(bearer);
|
|
46
|
+
if (claim && (await session.isAdmin(claim.email))) {
|
|
47
|
+
return { ok: true, credential: { kind: 'admin-session', email: claim.email } };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Nothing matched. Say WHY when the reason is the deployment's, not the
|
|
51
|
+
// caller's: a hook pointed at an unconfigured endpoint should read "set
|
|
52
|
+
// the secret", not "wrong secret".
|
|
53
|
+
if (!secret && (bearer || input.gitlabToken || input.hubSignature)) {
|
|
54
|
+
return { ok: false, status: 503, message: configured ? SECRET_TOO_SHORT : NO_SECRET };
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
ok: false,
|
|
58
|
+
status: 401,
|
|
59
|
+
message: 'A valid sync secret or an administrator session is required.',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=sync-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-auth.js","sourceRoot":"","sources":["../../../src/modules/kb-sync/sync-auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn whatever a caller POSTed to `/api/sync` into the branches to sync.
|
|
3
|
+
*
|
|
4
|
+
* Pure: no IO, no throwing on foreign shapes. The endpoint is meant to be
|
|
5
|
+
* pointed at by a webhook subscription whose payload the operator does not
|
|
6
|
+
* control, so an unrecognised body must degrade to "sync everything" — the
|
|
7
|
+
* safe answer, and the one an empty pipeline call gets too — never to a 400
|
|
8
|
+
* that makes the host disable the subscription.
|
|
9
|
+
*
|
|
10
|
+
* Recognised shapes:
|
|
11
|
+
*
|
|
12
|
+
* explicit `{ "branches": ["main", "ali/x"] }`
|
|
13
|
+
* azure-devops `eventType: git.push` → `resource.refUpdates[].name`
|
|
14
|
+
* `eventType: git.pullrequest.*` → `resource.targetRefName`
|
|
15
|
+
* and `resource.sourceRefName`
|
|
16
|
+
* github/gitea `{ "ref": "refs/heads/main" }` (push)
|
|
17
|
+
* gitlab `{ "object_kind": "push", "ref": "refs/heads/main" }`
|
|
18
|
+
*
|
|
19
|
+
* Only `refs/heads/*` refs count; a tag push names nothing to sync. A branch
|
|
20
|
+
* name git would refuse is dropped from a provider payload (the host
|
|
21
|
+
* produced it, nothing we can do) but reported for an explicit body, where
|
|
22
|
+
* it is the caller's mistake to fix.
|
|
23
|
+
*/
|
|
24
|
+
export type SyncPayloadSource = 'explicit' | 'azure-devops' | 'github' | 'gitlab' | 'none';
|
|
25
|
+
export interface ParsedSyncPayload {
|
|
26
|
+
source: SyncPayloadSource;
|
|
27
|
+
branches: string[] | 'all';
|
|
28
|
+
/** Explicit-body names git would refuse. Empty for every other source. */
|
|
29
|
+
invalid: string[];
|
|
30
|
+
}
|
|
31
|
+
export declare function parseSyncPayload(body: unknown): ParsedSyncPayload;
|
|
32
|
+
//# sourceMappingURL=sync-payload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-payload.d.ts","sourceRoot":"","sources":["../../../src/modules/kb-sync/sync-payload.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE3F,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IAC3B,0EAA0E;IAC1E,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AA+CD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,iBAAiB,CA+DjE"}
|