@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,107 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import { walkKb, type KbWalkListener } from '../kb-walk.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The one walk: what it skips, in what order it visits, what it calls a hole
|
|
9
|
+
* — and that every listener on it is told exactly the same things.
|
|
10
|
+
*/
|
|
11
|
+
describe('walkKb', () => {
|
|
12
|
+
let root: string;
|
|
13
|
+
const write = async (rel: string, text = '') => {
|
|
14
|
+
const abs = path.join(root, rel);
|
|
15
|
+
await fs.mkdir(path.dirname(abs), { recursive: true });
|
|
16
|
+
await fs.writeFile(abs, text);
|
|
17
|
+
};
|
|
18
|
+
/** A listener that records every event it is told, in order. */
|
|
19
|
+
const recorder = () => {
|
|
20
|
+
const events: string[] = [];
|
|
21
|
+
const listener: KbWalkListener = {
|
|
22
|
+
onDir: (rel, entries) => void events.push(`dir ${rel || '.'} [${entries.map((e) => e.name).join(' ')}]`),
|
|
23
|
+
onFile: (dir, name) => void events.push(`file ${dir ? `${dir}/${name}` : name}`),
|
|
24
|
+
onHole: (rel) => void events.push(`hole ${rel || '.'}`),
|
|
25
|
+
};
|
|
26
|
+
return { events, listener };
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
beforeEach(async () => {
|
|
30
|
+
root = await fs.mkdtemp(path.join(os.tmpdir(), 'kb-walk-'));
|
|
31
|
+
});
|
|
32
|
+
afterEach(() => fs.rm(root, { recursive: true, force: true }));
|
|
33
|
+
|
|
34
|
+
it('visits every folder once, in component order, skipping dot-entries and node_modules', async () => {
|
|
35
|
+
await write('Plugins/a-b/plugin.json');
|
|
36
|
+
await write('Plugins/a/b/plugin.json');
|
|
37
|
+
await write('KnowledgeBase/x.md');
|
|
38
|
+
await write('.git/HEAD');
|
|
39
|
+
await write('node_modules/dep/index.js');
|
|
40
|
+
await write('Plugins/.parked/plugin.json');
|
|
41
|
+
const { events, listener } = recorder();
|
|
42
|
+
const { holes } = await walkKb(root, [listener]);
|
|
43
|
+
expect(holes).toEqual([]);
|
|
44
|
+
expect(events).toEqual([
|
|
45
|
+
'dir . [KnowledgeBase Plugins]',
|
|
46
|
+
'dir KnowledgeBase [x.md]',
|
|
47
|
+
'file KnowledgeBase/x.md',
|
|
48
|
+
'dir Plugins [a a-b]', // "a" before "a-b": the walk order every consumer agrees on
|
|
49
|
+
'dir Plugins/a [b]',
|
|
50
|
+
'dir Plugins/a/b [plugin.json]',
|
|
51
|
+
'file Plugins/a/b/plugin.json',
|
|
52
|
+
'dir Plugins/a-b [plugin.json]',
|
|
53
|
+
'file Plugins/a-b/plugin.json',
|
|
54
|
+
]);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('tells every listener the same things', async () => {
|
|
58
|
+
await write('Plugins/GTM/plugin.json');
|
|
59
|
+
await write('Skills/Eng/deploy/SKILL.md');
|
|
60
|
+
const a = recorder();
|
|
61
|
+
const b = recorder();
|
|
62
|
+
await walkKb(root, [a.listener, b.listener]);
|
|
63
|
+
expect(a.events.length).toBeGreaterThan(0);
|
|
64
|
+
expect(b.events).toEqual(a.events);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('a folder that exists but cannot be listed is a hole — reported to every listener and in the result', async () => {
|
|
68
|
+
await write('Plugins/locked/plugin.json');
|
|
69
|
+
await write('Plugins/open/plugin.json');
|
|
70
|
+
const real = fs.readdir;
|
|
71
|
+
const spy = vi.spyOn(fs, 'readdir').mockImplementation(((dir: string, opts: unknown) =>
|
|
72
|
+
String(dir).endsWith('locked')
|
|
73
|
+
? Promise.reject(Object.assign(new Error('EACCES: permission denied'), { code: 'EACCES' }))
|
|
74
|
+
: (real as (d: string, o: unknown) => Promise<unknown>).call(fs, dir, opts)) as never);
|
|
75
|
+
try {
|
|
76
|
+
const { events, listener } = recorder();
|
|
77
|
+
const { holes } = await walkKb(root, [listener]);
|
|
78
|
+
expect(holes).toEqual(['Plugins/locked']);
|
|
79
|
+
expect(events).toContain('hole Plugins/locked');
|
|
80
|
+
// The rest of the tree is still walked.
|
|
81
|
+
expect(events).toContain('file Plugins/open/plugin.json');
|
|
82
|
+
} finally {
|
|
83
|
+
spy.mockRestore();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('a missing root is an empty walk; a folder that vanished between listing and visiting is not a hole', async () => {
|
|
88
|
+
const empty = recorder();
|
|
89
|
+
expect(await walkKb(path.join(root, 'nope'), [empty.listener])).toEqual({ holes: [] });
|
|
90
|
+
expect(empty.events).toEqual([]);
|
|
91
|
+
|
|
92
|
+
await write('Plugins/gone/plugin.json');
|
|
93
|
+
const real = fs.readdir;
|
|
94
|
+
const spy = vi.spyOn(fs, 'readdir').mockImplementation(((dir: string, opts: unknown) =>
|
|
95
|
+
String(dir).endsWith('gone')
|
|
96
|
+
? Promise.reject(Object.assign(new Error('ENOENT: no such file'), { code: 'ENOENT' }))
|
|
97
|
+
: (real as (d: string, o: unknown) => Promise<unknown>).call(fs, dir, opts)) as never);
|
|
98
|
+
try {
|
|
99
|
+
const { events, listener } = recorder();
|
|
100
|
+
const { holes } = await walkKb(root, [listener]);
|
|
101
|
+
expect(holes).toEqual([]);
|
|
102
|
+
expect(events.some((e) => e.startsWith('hole'))).toBe(false);
|
|
103
|
+
} finally {
|
|
104
|
+
spy.mockRestore();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { comparePathComponents } from '../path-order.js';
|
|
3
|
+
|
|
4
|
+
describe('comparePathComponents', () => {
|
|
5
|
+
it('orders component by component, the way a sorted directory walk visits paths', () => {
|
|
6
|
+
// A whole-string compare would weigh "-" against "/" and put a-b/x first.
|
|
7
|
+
expect(comparePathComponents('a/b/x', 'a-b/x')).toBeLessThan(0);
|
|
8
|
+
expect(comparePathComponents('Plugins/GTM', 'Plugins/GTM/skills')).toBeLessThan(0);
|
|
9
|
+
expect(comparePathComponents('Plugins/GTM', 'Plugins/GTM')).toBe(0);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('never calls two distinct paths equal: a collation tie falls through to code-unit order', () => {
|
|
13
|
+
// Precomposed é and its decomposed spelling collate as equal; two folders
|
|
14
|
+
// so named must still have ONE first, or discovery and principal synthesis
|
|
15
|
+
// could each claim a different one for the same slug.
|
|
16
|
+
const composed = 'Plugins/café';
|
|
17
|
+
const decomposed = 'Plugins/café';
|
|
18
|
+
// (Whether the host's collation equates them is the host's business —
|
|
19
|
+
// the property under test holds either way: distinct paths never tie.)
|
|
20
|
+
const order = comparePathComponents(composed, decomposed);
|
|
21
|
+
expect(order).not.toBe(0);
|
|
22
|
+
expect(comparePathComponents(decomposed, composed)).toBe(-order);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { TokenCrypto } from '../token-crypto.js';
|
|
3
|
+
import { randomBytes } from 'node:crypto';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The contract an operator meets at boot: a wrong-length key must name the
|
|
7
|
+
* variable THEY set. The error used to hardcode `SHAREPOINT_TOKEN_ENC_KEY` —
|
|
8
|
+
* the primitive's birthplace — and a core deployment failing on its vault key
|
|
9
|
+
* was told to fix a SharePoint variable it never configured.
|
|
10
|
+
*/
|
|
11
|
+
describe('TokenCrypto key validation', () => {
|
|
12
|
+
// 30 bytes — the classic wrong key: 40 base64 chars instead of 44.
|
|
13
|
+
const SHORT = randomBytes(30).toString('base64');
|
|
14
|
+
|
|
15
|
+
it('names SECRETS_ENC_KEY by default — the variable every core consumer reads', () => {
|
|
16
|
+
expect(() => new TokenCrypto(SHORT)).toThrowError(
|
|
17
|
+
/^SECRETS_ENC_KEY must decode to 32 bytes \(got 30\)/,
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('names the variable an overlay passes for its own key', () => {
|
|
22
|
+
expect(() => new TokenCrypto(SHORT, 'SHAREPOINT_TOKEN_ENC_KEY')).toThrowError(
|
|
23
|
+
/^SHAREPOINT_TOKEN_ENC_KEY must decode to 32 bytes/,
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('accepts a 32-byte key as base64 and as hex, and round-trips', () => {
|
|
28
|
+
const raw = randomBytes(32);
|
|
29
|
+
for (const encoded of [raw.toString('base64'), raw.toString('hex')]) {
|
|
30
|
+
const crypto = new TokenCrypto(encoded);
|
|
31
|
+
expect(crypto.decrypt(crypto.encrypt('s3cret'))).toBe('s3cret');
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { TtlCache } from '../ttl-cache.js';
|
|
3
|
+
|
|
4
|
+
describe('TtlCache', () => {
|
|
5
|
+
it('serves a value until the TTL runs out, then stops', () => {
|
|
6
|
+
let now = 0;
|
|
7
|
+
const cache = new TtlCache<string>(1000, () => now);
|
|
8
|
+
cache.set('v', cache.begin());
|
|
9
|
+
expect(cache.get()).toBe('v');
|
|
10
|
+
now = 999;
|
|
11
|
+
expect(cache.get()).toBe('v');
|
|
12
|
+
now = 1000;
|
|
13
|
+
expect(cache.get()).toBeNull();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('invalidate() drops the value immediately', () => {
|
|
17
|
+
const cache = new TtlCache<string>(1000, () => 0);
|
|
18
|
+
cache.set('v', cache.begin());
|
|
19
|
+
cache.invalidate();
|
|
20
|
+
expect(cache.get()).toBeNull();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The race the token exists for, and the one that made the merge fix a
|
|
25
|
+
* no-op in exactly the case it targeted: a catalog read starts on the
|
|
26
|
+
* pre-merge tree, the merge's pull lands and drops the cache, and the
|
|
27
|
+
* in-flight read then writes its stale list back with a fresh full TTL —
|
|
28
|
+
* silently undoing the drop.
|
|
29
|
+
*/
|
|
30
|
+
it('discards a value whose fetch straddled an invalidate()', () => {
|
|
31
|
+
const cache = new TtlCache<string>(1000, () => 0);
|
|
32
|
+
const token = cache.begin(); // read starts on the old tree
|
|
33
|
+
cache.invalidate(); // the pull lands mid-read
|
|
34
|
+
cache.set('stale', token); // the read comes back and tries to store
|
|
35
|
+
expect(cache.get()).toBeNull();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('stores a value whose fetch did not straddle an invalidate()', () => {
|
|
39
|
+
const cache = new TtlCache<string>(1000, () => 0);
|
|
40
|
+
cache.invalidate();
|
|
41
|
+
const token = cache.begin();
|
|
42
|
+
cache.set('fresh', token);
|
|
43
|
+
expect(cache.get()).toBe('fresh');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('keeps refusing a token from before the drop, not just the first time', () => {
|
|
47
|
+
const cache = new TtlCache<string>(1000, () => 0);
|
|
48
|
+
const stale = cache.begin();
|
|
49
|
+
cache.invalidate();
|
|
50
|
+
const fresh = cache.begin();
|
|
51
|
+
cache.set('fresh', fresh);
|
|
52
|
+
cache.set('stale', stale);
|
|
53
|
+
expect(cache.get()).toBe('fresh');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -166,6 +166,35 @@ export class PullRebaseConflictError extends WorkflowDomainError {
|
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
/**
|
|
170
|
+
* The clone's branch no longer exists on origin: the fetch that refreshes
|
|
171
|
+
* `refs/remotes/origin/<branch>` found no such ref. Distinct from an
|
|
172
|
+
* unreachable remote — the host answered, and the answer was "gone" — so a
|
|
173
|
+
* caller can treat the clone as stale rather than the sync as failed. 410.
|
|
174
|
+
*/
|
|
175
|
+
export class RemoteBranchGoneError extends WorkflowDomainError {
|
|
176
|
+
readonly kind = 'remote-branch-gone' as const;
|
|
177
|
+
constructor(readonly branch: string) {
|
|
178
|
+
super(`Branch "${branch}" no longer exists on the remote.`, 410, {
|
|
179
|
+
kind: 'remote-branch-gone',
|
|
180
|
+
branch,
|
|
181
|
+
});
|
|
182
|
+
this.name = 'RemoteBranchGoneError';
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Whether a failed clone or fetch failed because origin has no such branch, as
|
|
188
|
+
* opposed to being unreachable or refusing the credential. Git's wording for
|
|
189
|
+
* the two shapes: `Remote branch <x> not found in upstream origin` (clone) and
|
|
190
|
+
* `couldn't find remote ref refs/heads/<x>` (fetch). The ONE classifier for
|
|
191
|
+
* this fact — the workspace bootstrap and the git layer both consult it, so
|
|
192
|
+
* a wording learned by one is learned by both.
|
|
193
|
+
*/
|
|
194
|
+
export function isMissingRemoteBranchFailure(message: string): boolean {
|
|
195
|
+
return /Remote branch .* not found in upstream|couldn't find remote ref/i.test(message);
|
|
196
|
+
}
|
|
197
|
+
|
|
169
198
|
/**
|
|
170
199
|
* Generic 400 for workflow-input validation (malformed branch names, missing
|
|
171
200
|
* fields, etc.). Carries an optional payload so callers can attach typed
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a filesystem error means "there is no such path" — the one failure
|
|
3
|
+
* a reader may treat as an ordinary answer. Everything else (permissions,
|
|
4
|
+
* I/O, a loop) is a failure to READ what is there, and a caller that folds
|
|
5
|
+
* it into absence turns an outage into a wrong answer.
|
|
6
|
+
*/
|
|
7
|
+
export function isAbsence(err: unknown): boolean {
|
|
8
|
+
const code = (err as { code?: unknown } | null)?.code;
|
|
9
|
+
return code === 'ENOENT' || code === 'ENOTDIR';
|
|
10
|
+
}
|
package/src/shared/fs-walk.ts
CHANGED
|
@@ -1,30 +1,45 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import { walkKb, isSkippedEntry } from './kb-walk.js';
|
|
2
|
+
|
|
3
|
+
export { isSkippedEntry };
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
* The
|
|
6
|
-
* sorted) paths of files under `root` whose basename matches.
|
|
7
|
-
* yields `[]`;
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* The file listing catalog scanners want, on top of {@link walkKb}: relative
|
|
7
|
+
* (`/`-separated, sorted) paths of files under `root` whose basename matches.
|
|
8
|
+
* A missing root yields `[]`; skipped entries are never entered.
|
|
9
|
+
*
|
|
10
|
+
* A directory that cannot be listed is SKIPPED by default — right for a
|
|
11
|
+
* catalog, which shows what it can. A caller that must see EVERYTHING or
|
|
12
|
+
* nothing passes `strict`, and the listing throws a {@link WalkError} naming
|
|
13
|
+
* the directory instead: a list with a hole in it is not a list of every
|
|
14
|
+
* file, and the caller can say WHICH hole rather than pass a raw errno up.
|
|
10
15
|
*/
|
|
11
|
-
export async function walkFiles(
|
|
16
|
+
export async function walkFiles(
|
|
17
|
+
root: string,
|
|
18
|
+
match: (basename: string) => boolean,
|
|
19
|
+
opts: { strict?: boolean } = {},
|
|
20
|
+
): Promise<string[]> {
|
|
12
21
|
const out: string[] = [];
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (entry.isDirectory()) await walk(path.join(dir, entry.name), childRel);
|
|
24
|
-
else if (entry.isFile() && match(entry.name)) out.push(childRel);
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
await walk(root, '');
|
|
22
|
+
await walkKb(root, [
|
|
23
|
+
{
|
|
24
|
+
onFile(dir, name) {
|
|
25
|
+
if (match(name)) out.push(dir ? `${dir}/${name}` : name);
|
|
26
|
+
},
|
|
27
|
+
onHole(rel, err) {
|
|
28
|
+
if (opts.strict) throw new WalkError(rel, err);
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
]);
|
|
28
32
|
out.sort();
|
|
29
33
|
return out;
|
|
30
34
|
}
|
|
35
|
+
|
|
36
|
+
/** A strict listing's refusal: the directory (relative to the root, `''` for the root itself) it could not list. */
|
|
37
|
+
export class WalkError extends Error {
|
|
38
|
+
constructor(
|
|
39
|
+
readonly relDir: string,
|
|
40
|
+
readonly cause: unknown,
|
|
41
|
+
) {
|
|
42
|
+
super(`${relDir || '.'} could not be listed — ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
43
|
+
this.name = 'WalkError';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import { isAbsence } from './fs-errors.js';
|
|
4
|
+
import { comparePathComponents } from './path-order.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* THE walk of a knowledge-base checkout.
|
|
8
|
+
*
|
|
9
|
+
* Every reader of the tree — the access resolver looking for rules, plugin
|
|
10
|
+
* discovery looking for manifests, a rename looking for grants, the catalog
|
|
11
|
+
* scanners — used to carry a recursive `readdir` loop of its own, each with
|
|
12
|
+
* its own skip list, its own idea of what an unreadable folder means and its
|
|
13
|
+
* own visiting order. Two loops that disagree on one of those are two
|
|
14
|
+
* answers to "what is in the tree", and every such disagreement was a bug.
|
|
15
|
+
*
|
|
16
|
+
* So there is one loop, and it decides three things once:
|
|
17
|
+
*
|
|
18
|
+
* - WHAT IS SKIPPED: {@link isSkippedEntry} — dot-entries (`.git`, a parked
|
|
19
|
+
* delete) and vendored dependencies. Nothing else is pruned; a listener
|
|
20
|
+
* that stops at a leaf of its own (a skill folder, a plugin folder)
|
|
21
|
+
* simply ignores what lies beneath it.
|
|
22
|
+
* - IN WHAT ORDER: each folder's entries in {@link comparePathComponents}
|
|
23
|
+
* order, so "first by path" means the same thing to every listener.
|
|
24
|
+
* - WHAT A HOLE IS: a folder that exists but could not be listed is
|
|
25
|
+
* reported as an event and in the result — never thrown, never silently
|
|
26
|
+
* skipped — so a reader can show what it saw and a writer can refuse.
|
|
27
|
+
* A folder that vanished between listing and visiting is not a hole.
|
|
28
|
+
*
|
|
29
|
+
* Listeners see the same events in the same order; what each makes of them
|
|
30
|
+
* is its own business (`.bevelignore` included — the explorer honours it,
|
|
31
|
+
* the resolver must not, so it is a listener's decision, not the walk's).
|
|
32
|
+
*/
|
|
33
|
+
export function isSkippedEntry(name: string): boolean {
|
|
34
|
+
return name.startsWith('.') || name === 'node_modules';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A directory entry as the walk presents it: a name and what it is. */
|
|
38
|
+
export interface WalkedEntry {
|
|
39
|
+
name: string;
|
|
40
|
+
isDirectory(): boolean;
|
|
41
|
+
isFile(): boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface KbWalkListener {
|
|
45
|
+
/** A folder was listed. `rel` is repo-relative (`''` for the root); `entries` are its children, skipped ones removed, in walk order. */
|
|
46
|
+
onDir?(rel: string, entries: readonly WalkedEntry[]): void | Promise<void>;
|
|
47
|
+
/** A file was seen. `dir` is its folder (`''` for the root), `name` its basename. */
|
|
48
|
+
onFile?(dir: string, name: string): void | Promise<void>;
|
|
49
|
+
/** A folder that exists but could not be listed. */
|
|
50
|
+
onHole?(rel: string, err: unknown): void | Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface KbWalkResult {
|
|
54
|
+
/** Every folder that could not be listed, repo-relative (`''` for the root). */
|
|
55
|
+
holes: string[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Walk the checkout at `root`, driving every listener from one traversal.
|
|
60
|
+
* A missing root is an empty tree. Listener errors propagate untouched.
|
|
61
|
+
*/
|
|
62
|
+
export async function walkKb(root: string, listeners: readonly KbWalkListener[]): Promise<KbWalkResult> {
|
|
63
|
+
const holes: string[] = [];
|
|
64
|
+
|
|
65
|
+
const visit = async (abs: string, rel: string): Promise<void> => {
|
|
66
|
+
let listed: import('node:fs').Dirent[];
|
|
67
|
+
try {
|
|
68
|
+
listed = await fs.readdir(abs, { withFileTypes: true });
|
|
69
|
+
} catch (err) {
|
|
70
|
+
if (isAbsence(err)) {
|
|
71
|
+
// The root: a checkout without this tree, an empty walk. Deeper: a
|
|
72
|
+
// folder that vanished between listing and visiting — not a hole.
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
holes.push(rel);
|
|
76
|
+
for (const l of listeners) await l.onHole?.(rel, err);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const entries: WalkedEntry[] = listed
|
|
80
|
+
.filter((e) => !isSkippedEntry(e.name) && (e.isDirectory() || e.isFile()))
|
|
81
|
+
.sort((a, b) => comparePathComponents(a.name, b.name));
|
|
82
|
+
for (const l of listeners) await l.onDir?.(rel, entries);
|
|
83
|
+
for (const entry of entries) {
|
|
84
|
+
const childRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
85
|
+
if (entry.isDirectory()) {
|
|
86
|
+
await visit(path.join(abs, entry.name), childRel);
|
|
87
|
+
} else {
|
|
88
|
+
for (const l of listeners) await l.onFile?.(rel, entry.name);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
await visit(root, '');
|
|
94
|
+
return { holes };
|
|
95
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE ordering of repository paths: component by component, each
|
|
3
|
+
* component by `localeCompare`, a shorter path before its extensions. It is
|
|
4
|
+
* exactly the order a directory walk that sorts each level's entries visits
|
|
5
|
+
* them in — so a consumer that must agree with the walk on "first by path"
|
|
6
|
+
* (plugin discovery keeps the first of two folders sharing a slug; principal
|
|
7
|
+
* synthesis must pick the same one) sorts with this and nothing else. A
|
|
8
|
+
* whole-string `localeCompare` does not agree: it weighs the separator
|
|
9
|
+
* against the next character (`a-b/x` before `a/b/x`), the walk never does.
|
|
10
|
+
*
|
|
11
|
+
* TOTAL over distinct paths: two components the collation cannot tell apart
|
|
12
|
+
* (a precomposed `é` and its decomposed spelling) fall through to code-unit
|
|
13
|
+
* order, so no pair of distinct paths ever compares equal — an equal pair
|
|
14
|
+
* would leave "first" to whichever enumeration happened to run, and the two
|
|
15
|
+
* consumers could then pick different folders for one slug.
|
|
16
|
+
*/
|
|
17
|
+
export function comparePathComponents(a: string, b: string): number {
|
|
18
|
+
const as = a.split('/');
|
|
19
|
+
const bs = b.split('/');
|
|
20
|
+
const n = Math.min(as.length, bs.length);
|
|
21
|
+
for (let i = 0; i < n; i++) {
|
|
22
|
+
const order = compareComponent(as[i]!, bs[i]!);
|
|
23
|
+
if (order !== 0) return order;
|
|
24
|
+
}
|
|
25
|
+
return as.length - bs.length;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** One component against another: collation first, code units to break a tie. */
|
|
29
|
+
function compareComponent(a: string, b: string): number {
|
|
30
|
+
const order = a.localeCompare(b);
|
|
31
|
+
if (order !== 0) return order;
|
|
32
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
33
|
+
}
|
|
@@ -11,16 +11,16 @@ import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
|
|
|
11
11
|
export class TokenCrypto {
|
|
12
12
|
private readonly key: Buffer;
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
this.key =
|
|
14
|
+
/**
|
|
15
|
+
* @param rawKey 32-byte key as hex (64 chars) or base64.
|
|
16
|
+
* @param envVarName the environment variable the caller read `rawKey` from,
|
|
17
|
+
* so a bad key is reported against the variable the operator actually has
|
|
18
|
+
* to fix. Defaults to core's own `SECRETS_ENC_KEY` — every core consumer
|
|
19
|
+
* reads that; an overlay bringing its own key (the SharePoint token
|
|
20
|
+
* cache, connector configs) passes its own name.
|
|
21
|
+
*/
|
|
22
|
+
constructor(rawKey: string, envVarName = 'SECRETS_ENC_KEY') {
|
|
23
|
+
this.key = assertKeyDecodesTo32Bytes(rawKey, envVarName);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
encrypt(plaintext: string): string {
|
|
@@ -52,3 +52,21 @@ function decodeKey(raw: string): Buffer {
|
|
|
52
52
|
if (/^[0-9a-fA-F]{64}$/.test(raw)) return Buffer.from(raw, 'hex');
|
|
53
53
|
return Buffer.from(raw, 'base64');
|
|
54
54
|
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Decode a key and refuse anything that is not exactly 32 bytes, blaming
|
|
58
|
+
* `envVarName`. Exported so boot-time config can validate the key THE MOMENT
|
|
59
|
+
* it knows which environment variable supplied it (`SECRETS_ENC_KEY` or a
|
|
60
|
+
* legacy fallback) — every later `new TokenCrypto(key)` with the default name
|
|
61
|
+
* is then safe, because a bad key never gets that far.
|
|
62
|
+
*/
|
|
63
|
+
export function assertKeyDecodesTo32Bytes(rawKey: string, envVarName: string): Buffer {
|
|
64
|
+
const key = decodeKey(rawKey);
|
|
65
|
+
if (key.length !== 32) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`${envVarName} must decode to 32 bytes (got ${key.length}). ` +
|
|
68
|
+
'Generate one with: `node -e "console.log(require(\'crypto\').randomBytes(32).toString(\'base64\'))"`.',
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return key;
|
|
72
|
+
}
|
package/src/shared/ttl-cache.ts
CHANGED
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export class TtlCache<T> {
|
|
8
8
|
private entry: { at: number; value: T } | null = null;
|
|
9
|
+
/**
|
|
10
|
+
* Bumped by every `invalidate()`. A fetch carries the value this had when it
|
|
11
|
+
* started, and `set` refuses a value whose fetch straddled a drop — see there.
|
|
12
|
+
*/
|
|
13
|
+
private generation = 0;
|
|
9
14
|
|
|
10
15
|
constructor(
|
|
11
16
|
private readonly ttlMs: number,
|
|
@@ -17,11 +22,38 @@ export class TtlCache<T> {
|
|
|
17
22
|
return null;
|
|
18
23
|
}
|
|
19
24
|
|
|
20
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Take a token BEFORE starting the fetch whose result you will `set`.
|
|
27
|
+
*
|
|
28
|
+
* Every caller of this cache reads the disk across an `await`, and the whole
|
|
29
|
+
* point of `invalidate()` is that it fires during exactly that window: the
|
|
30
|
+
* merge pulls the new tree in while a catalog read that started on the old
|
|
31
|
+
* one is still in flight. Without a token that late `set` wins, and the cache
|
|
32
|
+
* holds the PRE-invalidation scan for a fresh full TTL — the drop silently
|
|
33
|
+
* undone, and the staleness it existed to prevent restored.
|
|
34
|
+
*/
|
|
35
|
+
begin(): number {
|
|
36
|
+
return this.generation;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Store a value fetched under `token`. Required, not optional: a caller that
|
|
41
|
+
* could forget it is a caller that reintroduces the race above, and there are
|
|
42
|
+
* few enough call sites to make the token cheap.
|
|
43
|
+
*
|
|
44
|
+
* A stale token means an `invalidate()` landed mid-fetch, so this value
|
|
45
|
+
* describes a tree that is already gone: it is DISCARDED rather than stored.
|
|
46
|
+
* The fetcher still returns it to its own caller — that read is merely as old
|
|
47
|
+
* as the moment it started, which is true of any read — but the next caller
|
|
48
|
+
* re-scans instead of inheriting it.
|
|
49
|
+
*/
|
|
50
|
+
set(value: T, token: number): void {
|
|
51
|
+
if (token !== this.generation) return;
|
|
21
52
|
this.entry = { at: this.now(), value };
|
|
22
53
|
}
|
|
23
54
|
|
|
24
55
|
invalidate(): void {
|
|
25
56
|
this.entry = null;
|
|
57
|
+
this.generation++;
|
|
26
58
|
}
|
|
27
59
|
}
|