@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,368 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
|
|
6
|
+
import type { WorkspaceService } from '../../workspace/workspace.service.js';
|
|
7
|
+
import { AccessControlService } from '../access-control.service.js';
|
|
8
|
+
import { parseAccessEntry } from '../../access-model/access-grammar.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* `plugin/<Name>/<verb>` as a grantable principal: derived from the plugin
|
|
12
|
+
* folder's own access.md, resolved like any group, and usable on ANY path —
|
|
13
|
+
* a shared skill under `Skills/`, a Knowledge folder, anything. These tests
|
|
14
|
+
* drive the public resolver (`canRead`/`canWrite`, `eligibleReaders`,
|
|
15
|
+
* `kbPrincipals`) over a real on-disk tree; the synthesis is not tested
|
|
16
|
+
* through its own helper.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const KB_DIR = 'knowledge-base';
|
|
20
|
+
|
|
21
|
+
function stubWorkspaceService(workspaceId: string, workspaceDir: string): WorkspaceService {
|
|
22
|
+
return {
|
|
23
|
+
getWorkspacePath: async (id: string) => {
|
|
24
|
+
if (id !== workspaceId) throw new Error(`unexpected workspace ${id}`);
|
|
25
|
+
return workspaceDir;
|
|
26
|
+
},
|
|
27
|
+
ensureRemotesFetched: async () => undefined,
|
|
28
|
+
} as unknown as WorkspaceService;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const ROLES_YAML = `roles:
|
|
32
|
+
Admin:
|
|
33
|
+
- admin@x.io
|
|
34
|
+
Engineer:
|
|
35
|
+
- eng@x.io
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
const GROUPS_YAML = `groups:
|
|
39
|
+
Sales Team:
|
|
40
|
+
- sam@x.io
|
|
41
|
+
- sue@x.io
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
/** A plugin folder's access.md in the body-governed format. */
|
|
45
|
+
function pluginAccessMd(body: string): string {
|
|
46
|
+
return `---\nread:\n - everyone\n---\n${body}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe('plugin principals', () => {
|
|
50
|
+
let root: string;
|
|
51
|
+
const workspaceId = 'ws-plugins-1';
|
|
52
|
+
|
|
53
|
+
beforeEach(async () => {
|
|
54
|
+
root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-plugin-principals-'));
|
|
55
|
+
});
|
|
56
|
+
afterEach(async () => {
|
|
57
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
async function makeService(files: Record<string, string>) {
|
|
61
|
+
const workspaceDir = path.join(root, workspaceId);
|
|
62
|
+
const repo = path.join(workspaceDir, KB_DIR);
|
|
63
|
+
for (const [rel, contents] of Object.entries(files)) {
|
|
64
|
+
const abs = path.join(repo, rel);
|
|
65
|
+
await fs.mkdir(path.dirname(abs), { recursive: true });
|
|
66
|
+
await fs.writeFile(abs, contents);
|
|
67
|
+
}
|
|
68
|
+
return new AccessControlService(stubWorkspaceService(workspaceId, workspaceDir), KB_DIR);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// A plugin IS a folder with a manifest; its access.md is its roster.
|
|
72
|
+
const BASE = {
|
|
73
|
+
'roles.yaml': ROLES_YAML,
|
|
74
|
+
'groups.yaml': GROUPS_YAML,
|
|
75
|
+
'access.md': '---\nwrite:\n - Admin\n---\n',
|
|
76
|
+
'Plugins/GTM/plugin.json': '{"name":"gtm"}',
|
|
77
|
+
'Plugins/GTM/access.md': pluginAccessMd(
|
|
78
|
+
'read:\n - Sales Team\n - Ali <ali@x.io>\nwrite:\n - Engineer\nowner:\n - Owen <owen@x.io>\n',
|
|
79
|
+
),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
describe('grammar', () => {
|
|
83
|
+
it('canonicalises the name half to the manifest slug and keeps the verb', () => {
|
|
84
|
+
const parsed = parseAccessEntry('plugin/Sales Team/read');
|
|
85
|
+
expect(parsed.ok && parsed.entry.kind === 'role' && parsed.entry.role).toBe('plugin/sales-team/read');
|
|
86
|
+
const upper = parseAccessEntry('Plugin/GTM/Write');
|
|
87
|
+
expect(upper.ok && upper.entry.kind === 'role' && upper.entry.role).toBe('plugin/gtm/write');
|
|
88
|
+
const denied = parseAccessEntry('deny plugin/GTM/owner');
|
|
89
|
+
expect(denied.ok && denied.entry.kind === 'role' && denied.entry.deny).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('rejects a token with a missing or unknown verb, naming the valid shapes', () => {
|
|
93
|
+
for (const bad of ['plugin/GTM', 'plugin/GTM/', 'plugin/GTM/admin', 'plugin//read']) {
|
|
94
|
+
const parsed = parseAccessEntry(bad);
|
|
95
|
+
expect(parsed.ok, bad).toBe(false);
|
|
96
|
+
expect(!parsed.ok && parsed.error).toMatch(/plugin\/<plugin>\/read/);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('resolution', () => {
|
|
102
|
+
it('grants a shared skill to the plugin\'s readers, writers and owners — and to nobody else', async () => {
|
|
103
|
+
const svc = await makeService({
|
|
104
|
+
...BASE,
|
|
105
|
+
'Skills/Eng/deploy/access.md': '---\n---\nread:\n - plugin/GTM/read\n',
|
|
106
|
+
});
|
|
107
|
+
const skill = 'Skills/Eng/deploy/SKILL.md';
|
|
108
|
+
// Group member, direct reader, writer (write implies read), owner.
|
|
109
|
+
for (const email of ['sam@x.io', 'ali@x.io', 'eng@x.io', 'owen@x.io']) {
|
|
110
|
+
expect(await svc.canRead(workspaceId, email, skill), email).toBe(true);
|
|
111
|
+
}
|
|
112
|
+
expect(await svc.canRead(workspaceId, 'mallory@x.io', skill)).toBe(false);
|
|
113
|
+
// Read on the plugin is read on the skill, nothing more.
|
|
114
|
+
expect(await svc.canWrite(workspaceId, 'sam@x.io', skill)).toBe(false);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('the write token names the plugin\'s writers and owners only', async () => {
|
|
118
|
+
const svc = await makeService({
|
|
119
|
+
...BASE,
|
|
120
|
+
'Skills/Eng/deploy/access.md': '---\n---\nread:\n - plugin/GTM/read\nwrite:\n - plugin/GTM/write\n',
|
|
121
|
+
});
|
|
122
|
+
const skill = 'Skills/Eng/deploy/SKILL.md';
|
|
123
|
+
expect(await svc.canWrite(workspaceId, 'eng@x.io', skill)).toBe(true);
|
|
124
|
+
expect(await svc.canWrite(workspaceId, 'owen@x.io', skill)).toBe(true);
|
|
125
|
+
expect(await svc.canWrite(workspaceId, 'sam@x.io', skill)).toBe(false);
|
|
126
|
+
expect(await svc.canWrite(workspaceId, 'ali@x.io', skill)).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('the owner token names owners alone', async () => {
|
|
130
|
+
const svc = await makeService({
|
|
131
|
+
...BASE,
|
|
132
|
+
'KnowledgeBase/Ops/access.md': '---\n---\nread:\n - plugin/GTM/owner\n',
|
|
133
|
+
});
|
|
134
|
+
expect(await svc.canRead(workspaceId, 'owen@x.io', 'KnowledgeBase/Ops/x.md')).toBe(true);
|
|
135
|
+
expect(await svc.canRead(workspaceId, 'eng@x.io', 'KnowledgeBase/Ops/x.md')).toBe(false);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('follows the plugin\'s roster live: a member removed from the plugin loses the skill', async () => {
|
|
139
|
+
const files = {
|
|
140
|
+
...BASE,
|
|
141
|
+
'Skills/Eng/deploy/access.md': '---\n---\nread:\n - plugin/GTM/read\n',
|
|
142
|
+
};
|
|
143
|
+
const svc = await makeService(files);
|
|
144
|
+
const skill = 'Skills/Eng/deploy/SKILL.md';
|
|
145
|
+
expect(await svc.canRead(workspaceId, 'ali@x.io', skill)).toBe(true);
|
|
146
|
+
|
|
147
|
+
// Drop Ali from the plugin's own access.md — nothing on the skill changes.
|
|
148
|
+
await fs.writeFile(
|
|
149
|
+
path.join(root, workspaceId, KB_DIR, 'Plugins/GTM/access.md'),
|
|
150
|
+
pluginAccessMd('read:\n - Sales Team\nwrite:\n - Engineer\n'),
|
|
151
|
+
);
|
|
152
|
+
svc.invalidate(workspaceId);
|
|
153
|
+
expect(await svc.canRead(workspaceId, 'ali@x.io', skill)).toBe(false);
|
|
154
|
+
expect(await svc.canRead(workspaceId, 'sam@x.io', skill)).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('a public WRITE principal makes the path writable by everyone — and the eligible set says so', async () => {
|
|
158
|
+
// A gate that counts approvers must not see "a principal with no
|
|
159
|
+
// enumerable members" where the truth is "anyone signed in".
|
|
160
|
+
const svc = await makeService({
|
|
161
|
+
...BASE,
|
|
162
|
+
'Plugins/Open/plugin.json': '{"name":"open"}',
|
|
163
|
+
'Plugins/Open/access.md': pluginAccessMd('read:\n - everyone\nwrite:\n - everyone\n'),
|
|
164
|
+
'Skills/Common/tips/access.md': '---\n---\nwrite:\n - plugin/Open/write\n',
|
|
165
|
+
});
|
|
166
|
+
const writers = await svc.eligibleWriters(workspaceId, 'Skills/Common/tips/SKILL.md');
|
|
167
|
+
expect(writers.roles).toContain('everyone');
|
|
168
|
+
expect(await svc.canWrite(workspaceId, 'nobody@elsewhere.io', 'Skills/Common/tips/SKILL.md')).toBe(true);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('two folders folding to one slug: the first by path claims it, roster or none — a twin never supplies the members', async () => {
|
|
172
|
+
// Discovery keeps the first folder by path and skips its twin; the
|
|
173
|
+
// principal follows the same rule, so the twin's roster never becomes
|
|
174
|
+
// the grant's membership through the back door.
|
|
175
|
+
const svc = await makeService({
|
|
176
|
+
...BASE,
|
|
177
|
+
'Plugins/a/GTM/plugin.json': '{"name":"gtm"}',
|
|
178
|
+
'Plugins/b/GTM/plugin.json': '{"name":"gtm"}',
|
|
179
|
+
'Plugins/b/GTM/access.md': pluginAccessMd('read:\n - Zed <zed@x.io>\n'),
|
|
180
|
+
'Skills/Common/tips/access.md': '---\n---\nread:\n - plugin/GTM/read\n',
|
|
181
|
+
});
|
|
182
|
+
// Path order is the walk's order: a/GTM comes first, claims the slug
|
|
183
|
+
// and has no roster — so the principal is EMPTY. Neither the root-level
|
|
184
|
+
// GTM's Ali nor the later twin's Zed gets in through it.
|
|
185
|
+
expect(await svc.canRead(workspaceId, 'ali@x.io', 'Skills/Common/tips/SKILL.md')).toBe(false);
|
|
186
|
+
expect(await svc.canRead(workspaceId, 'zed@x.io', 'Skills/Common/tips/SKILL.md')).toBe(false);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('a public READ principal makes the path unrestricted — one truth for the flag and the lists', async () => {
|
|
190
|
+
const svc = await makeService({
|
|
191
|
+
...BASE,
|
|
192
|
+
'Plugins/Open/plugin.json': '{"name":"open"}',
|
|
193
|
+
'Plugins/Open/access.md': pluginAccessMd('read:\n - everyone\nwrite:\n - Admin\n'),
|
|
194
|
+
'Skills/Common/tips/access.md': '---\n---\nread:\n - plugin/Open/read\n',
|
|
195
|
+
});
|
|
196
|
+
const readers = await svc.eligibleReaders(workspaceId, 'Skills/Common/tips/SKILL.md');
|
|
197
|
+
expect(readers.restricted).toBe(false);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('orders twins the way the walk does: component by component, never by the whole string', async () => {
|
|
201
|
+
// `a/b/GTM` is visited before `a-b/GTM` (the walk sorts each level:
|
|
202
|
+
// "a" < "a-b"); a whole-string compare would weigh "-" against "/" and
|
|
203
|
+
// could pick the other one — and with it the other roster.
|
|
204
|
+
const svc = await makeService({
|
|
205
|
+
'roles.yaml': ROLES_YAML,
|
|
206
|
+
'groups.yaml': GROUPS_YAML,
|
|
207
|
+
'access.md': '---\nwrite:\n - Admin\n---\n',
|
|
208
|
+
'Plugins/a/b/GTM/plugin.json': '{"name":"gtm"}',
|
|
209
|
+
'Plugins/a/b/GTM/access.md': pluginAccessMd('read:\n - Ali <ali@x.io>\n'),
|
|
210
|
+
'Plugins/a-b/GTM/plugin.json': '{"name":"gtm"}',
|
|
211
|
+
'Plugins/a-b/GTM/access.md': pluginAccessMd('read:\n - Zed <zed@x.io>\n'),
|
|
212
|
+
'Skills/Common/tips/access.md': '---\n---\nread:\n - plugin/GTM/read\n',
|
|
213
|
+
});
|
|
214
|
+
expect(await svc.canRead(workspaceId, 'ali@x.io', 'Skills/Common/tips/SKILL.md')).toBe(true);
|
|
215
|
+
expect(await svc.canRead(workspaceId, 'zed@x.io', 'Skills/Common/tips/SKILL.md')).toBe(false);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('a public principal is a verdict, not a line: nothing spells everyone, so nothing under that name is removable', async () => {
|
|
219
|
+
const svc = await makeService({
|
|
220
|
+
...BASE,
|
|
221
|
+
'Plugins/Open/plugin.json': '{"name":"open"}',
|
|
222
|
+
'Plugins/Open/access.md': pluginAccessMd('read:\n - everyone\nwrite:\n - Admin\n'),
|
|
223
|
+
'Skills/Common/tips/access.md': '---\n---\nread:\n - plugin/Open/read\n',
|
|
224
|
+
});
|
|
225
|
+
const skill = 'Skills/Common/tips/SKILL.md';
|
|
226
|
+
const readers = await svc.eligibleReaders(workspaceId, skill);
|
|
227
|
+
expect(readers.restricted).toBe(false);
|
|
228
|
+
// Public — and the lists still name the grant that makes it so, so the
|
|
229
|
+
// share dialog has a row to remove it by.
|
|
230
|
+
expect(readers.principals).toEqual([
|
|
231
|
+
{ name: 'Admin', kind: 'role' }, // the root's write, folded into read
|
|
232
|
+
{ name: 'everyone', kind: 'role' },
|
|
233
|
+
{ name: 'plugin/open/read', kind: 'plugin' },
|
|
234
|
+
]);
|
|
235
|
+
// And WHY it is public: the resolver names the public plugin principal,
|
|
236
|
+
// which the lists alone cannot tell from an ordinary one.
|
|
237
|
+
expect(readers.publicVia).toEqual(['plugin/open/read']);
|
|
238
|
+
// A revoke of `everyone` here would strip no line — so no source claims one.
|
|
239
|
+
expect(await svc.grantSources(workspaceId, 'file', skill, { kind: 'role', role: 'everyone' })).toEqual({});
|
|
240
|
+
// The plugin grant IS the line, and the one whose removal ends public read.
|
|
241
|
+
expect(await svc.grantSources(workspaceId, 'file', skill, { kind: 'role', role: 'plugin/open/read' })).toEqual({
|
|
242
|
+
read: [{ kind: 'ancestor', path: 'Skills/Common/tips/access.md' }],
|
|
243
|
+
});
|
|
244
|
+
// A literal grant, by contrast, is a removable line.
|
|
245
|
+
const literal = await makeService({
|
|
246
|
+
...BASE,
|
|
247
|
+
'Skills/Common/tips/access.md': '---\n---\nread:\n - everyone\n',
|
|
248
|
+
});
|
|
249
|
+
expect(await literal.grantSources(workspaceId, 'file', skill, { kind: 'role', role: 'everyone' })).toEqual({
|
|
250
|
+
read: [{ kind: 'ancestor', path: 'Skills/Common/tips/access.md' }],
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('a DENIAL of a public principal denies everyone — it beats a literal everyone grant beside it and a farther one', async () => {
|
|
255
|
+
// Every NAMED person still reads (Admin is granted by name, and a grant
|
|
256
|
+
// wins among a caller's own keys), so re-checking named people finds
|
|
257
|
+
// nothing wrong; only the derived everyone verdict can say that the
|
|
258
|
+
// unnamed caller — who holds the public key and nothing else — is denied.
|
|
259
|
+
const svc = await makeService({
|
|
260
|
+
// The admin is the only named person in the whole knowledge base.
|
|
261
|
+
'roles.yaml': 'roles:\n Admin:\n - admin@x.io\n',
|
|
262
|
+
'groups.yaml': 'groups: {}\n',
|
|
263
|
+
'access.md': '---\nwrite:\n - Admin\n---\n',
|
|
264
|
+
'Plugins/Open/plugin.json': '{"name":"open"}',
|
|
265
|
+
'Plugins/Open/access.md': pluginAccessMd('read:\n - everyone\nwrite:\n - Admin\n'),
|
|
266
|
+
// Public at the scope above; the folder itself carves everyone back out
|
|
267
|
+
// through the public principal, next to a literal grant that must lose.
|
|
268
|
+
'Skills/Common/access.md': '---\n---\nread:\n - everyone\n',
|
|
269
|
+
'Skills/Common/tips/access.md': '---\n---\nread:\n - everyone\n - Admin\n - deny plugin/Open/read\n',
|
|
270
|
+
});
|
|
271
|
+
const skill = 'Skills/Common/tips/SKILL.md';
|
|
272
|
+
expect(await svc.canRead(workspaceId, 'admin@x.io', skill)).toBe(true);
|
|
273
|
+
expect(await svc.canRead(workspaceId, 'nobody@elsewhere.io', skill)).toBe(false);
|
|
274
|
+
expect((await svc.eligibleReaders(workspaceId, skill)).restricted).toBe(true);
|
|
275
|
+
// The parent, where nothing denies, stays public.
|
|
276
|
+
expect((await svc.eligibleReaders(workspaceId, 'Skills/Common/SKILL.md')).restricted).toBe(false);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('a plugin readable by everyone yields a public principal', async () => {
|
|
280
|
+
const svc = await makeService({
|
|
281
|
+
...BASE,
|
|
282
|
+
'Plugins/Open/plugin.json': '{"name":"open"}',
|
|
283
|
+
'Plugins/Open/access.md': pluginAccessMd('read:\n - everyone\nwrite:\n - Admin\n'),
|
|
284
|
+
'Skills/Common/tips/access.md': '---\n---\nread:\n - plugin/Open/read\n',
|
|
285
|
+
});
|
|
286
|
+
expect(await svc.canRead(workspaceId, 'nobody@elsewhere.io', 'Skills/Common/tips/SKILL.md')).toBe(true);
|
|
287
|
+
// The write token of that plugin is still its writers only.
|
|
288
|
+
expect(await svc.canWrite(workspaceId, 'nobody@elsewhere.io', 'Skills/Common/tips/SKILL.md')).toBe(false);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('membership never counts the access.md frontmatter, a deny, or another plugin token', async () => {
|
|
292
|
+
const svc = await makeService({
|
|
293
|
+
...BASE,
|
|
294
|
+
// Frontmatter `read: everyone` is discoverability of the FILE. The body
|
|
295
|
+
// denies Sue, and points at another plugin's token.
|
|
296
|
+
'Plugins/Narrow/plugin.json': '{"name":"narrow"}',
|
|
297
|
+
'Plugins/Narrow/access.md': pluginAccessMd(
|
|
298
|
+
'read:\n - Sales Team\n - deny Sue <sue@x.io>\n - plugin/GTM/read\nwrite:\n - Admin\n',
|
|
299
|
+
),
|
|
300
|
+
'Skills/S/x/access.md': '---\n---\nread:\n - plugin/Narrow/read\n',
|
|
301
|
+
});
|
|
302
|
+
const skill = 'Skills/S/x/SKILL.md';
|
|
303
|
+
expect(await svc.canRead(workspaceId, 'sam@x.io', skill)).toBe(true);
|
|
304
|
+
// Deny entries are skipped, not subtracted: Sue is still in Sales Team.
|
|
305
|
+
expect(await svc.canRead(workspaceId, 'sue@x.io', skill)).toBe(true);
|
|
306
|
+
// GTM's reader Ali is NOT a member of Narrow through the nested token.
|
|
307
|
+
expect(await svc.canRead(workspaceId, 'ali@x.io', skill)).toBe(false);
|
|
308
|
+
// And nobody at random: the frontmatter `everyone` did not leak in.
|
|
309
|
+
expect(await svc.canRead(workspaceId, 'mallory@x.io', skill)).toBe(false);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('a token naming a plugin that does not exist is ignored like an unknown role', async () => {
|
|
313
|
+
const svc = await makeService({
|
|
314
|
+
...BASE,
|
|
315
|
+
'Skills/S/x/access.md': '---\n---\nread:\n - plugin/Ghost/read\n - Ali <ali@x.io>\n',
|
|
316
|
+
});
|
|
317
|
+
expect(await svc.canRead(workspaceId, 'ali@x.io', 'Skills/S/x/SKILL.md')).toBe(true);
|
|
318
|
+
expect(await svc.canRead(workspaceId, 'sam@x.io', 'Skills/S/x/SKILL.md')).toBe(false);
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
describe('display', () => {
|
|
323
|
+
it('eligibleReaders reports the token as a plugin-kind principal spelled by the identity, whatever the grant wrote', async () => {
|
|
324
|
+
const svc = await makeService({
|
|
325
|
+
...BASE,
|
|
326
|
+
// The folder's casing — how grants were written before the manifest became the identity.
|
|
327
|
+
'Skills/Eng/deploy/access.md': '---\n---\nread:\n - plugin/GTM/read\n',
|
|
328
|
+
});
|
|
329
|
+
const readers = await svc.eligibleReaders(workspaceId, 'Skills/Eng/deploy');
|
|
330
|
+
expect(readers.restricted).toBe(true);
|
|
331
|
+
expect(readers.principals).toContainEqual({ name: 'plugin/gtm/read', kind: 'plugin' });
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it('a plugin nested deeper than the root gets its principals too — a plugin is a folder with a manifest', async () => {
|
|
335
|
+
const svc = await makeService({
|
|
336
|
+
...BASE,
|
|
337
|
+
'Plugins/teams/Deep/plugin.json': '{"name":"deep"}',
|
|
338
|
+
'Plugins/teams/Deep/access.md': pluginAccessMd('read:\n - Ali <ali@x.io>\n'),
|
|
339
|
+
// A scope folder's own access.md is NOT a roster: no manifest beside it.
|
|
340
|
+
'Plugins/teams/access.md': '---\n---\nread:\n - everyone\n',
|
|
341
|
+
'Skills/S/x/access.md': '---\n---\nread:\n - plugin/Deep/read\n - plugin/teams/read\n',
|
|
342
|
+
});
|
|
343
|
+
expect(await svc.canRead(workspaceId, 'ali@x.io', 'Skills/S/x/SKILL.md')).toBe(true);
|
|
344
|
+
expect(await svc.canRead(workspaceId, 'mallory@x.io', 'Skills/S/x/SKILL.md')).toBe(false);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('kbPrincipals lists plugin folders once each, personal SHELVES excluded — by folder, never by name', async () => {
|
|
348
|
+
const svc = await makeService({
|
|
349
|
+
...BASE,
|
|
350
|
+
'Plugins/Ops/plugin.json': '{"name":"ops"}',
|
|
351
|
+
'Plugins/Ops/access.md': pluginAccessMd('read:\n - Admin\n'),
|
|
352
|
+
'Plugins/personal-abc123/plugin.json': '{"name":"personal-abc123"}',
|
|
353
|
+
'Plugins/personal-abc123/access.md': '---\n---\nread:\n - Ali <ali@x.io>\n',
|
|
354
|
+
// A plugin whose hand-written manifest name wears the prefix is a
|
|
355
|
+
// plugin all the same: the shelf is a place, decided by the folder.
|
|
356
|
+
'Plugins/Odd/plugin.json': '{"name":"personal-odd"}',
|
|
357
|
+
'Plugins/Odd/access.md': pluginAccessMd('read:\n - Admin\n'),
|
|
358
|
+
});
|
|
359
|
+
const { plugins } = await svc.kbPrincipals(workspaceId);
|
|
360
|
+
// Named by identity — the manifest — with the folder beside it.
|
|
361
|
+
expect([...plugins].sort((a, b) => a.folder.localeCompare(b.folder))).toEqual([
|
|
362
|
+
{ name: 'gtm', folder: 'Plugins/GTM' },
|
|
363
|
+
{ name: 'personal-odd', folder: 'Plugins/Odd' },
|
|
364
|
+
{ name: 'ops', folder: 'Plugins/Ops' },
|
|
365
|
+
]);
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
});
|
|
@@ -184,6 +184,14 @@ describe('GroupsAdminService', () => {
|
|
|
184
184
|
expect(await groupsYaml()).not.toContain('role/');
|
|
185
185
|
});
|
|
186
186
|
|
|
187
|
+
it("createGroup refuses the reserved 'plugin/' prefix — a group could otherwise pose as a plugin's members", async () => {
|
|
188
|
+
await expect(service.createGroup(ADMIN, 'plugin/GTM/read')).rejects.toMatchObject({
|
|
189
|
+
status: 422,
|
|
190
|
+
message: expect.stringContaining('plugin/'),
|
|
191
|
+
});
|
|
192
|
+
expect(await groupsYaml()).not.toContain('plugin/');
|
|
193
|
+
});
|
|
194
|
+
|
|
187
195
|
it('create → add → remove → delete lifecycle lands on disk', async () => {
|
|
188
196
|
await service.createGroup(ADMIN, 'Contractors');
|
|
189
197
|
await service.addMember(ADMIN, 'contractors', 'temp@x.io');
|
|
@@ -67,7 +67,7 @@ export type GrantPrincipal =
|
|
|
67
67
|
* returns it, and payload consumers fall back to `roles` (all treated as
|
|
68
68
|
* roles) when absent.
|
|
69
69
|
*/
|
|
70
|
-
export type ResolvedPrincipal = { name: string; kind: 'role' | 'group' };
|
|
70
|
+
export type ResolvedPrincipal = { name: string; kind: 'role' | 'group' | 'plugin' };
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
73
|
* Per-verb sources of a principal's access on a target. Only verbs the principal
|
|
@@ -199,11 +199,13 @@ export interface IAccessControl {
|
|
|
199
199
|
/**
|
|
200
200
|
* Answers the file viewer's "who can see this?" affordance. `restricted` is
|
|
201
201
|
* false only when `read: everyone` applies without an effective user-level
|
|
202
|
-
* denial — the node is readable by all signed-in users
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
202
|
+
* denial — the node is readable by all signed-in users. The lists name the
|
|
203
|
+
* principals (roles + direct users) granted read whether or not the node
|
|
204
|
+
* is public, with owners folded in (an `owner:` grant confers read); on a
|
|
205
|
+
* public node they include what makes it public. `publicVia` names the
|
|
206
|
+
* PUBLIC plugin principals granted read here — principals every signed-in
|
|
207
|
+
* user holds — so a caller can tell public-through-a-plugin from a literal
|
|
208
|
+
* `everyone` grant. The lists may be empty for a default-denied path.
|
|
207
209
|
*/
|
|
208
210
|
eligibleReaders(
|
|
209
211
|
workspaceId: string,
|
|
@@ -213,6 +215,7 @@ export interface IAccessControl {
|
|
|
213
215
|
principals?: ResolvedPrincipal[];
|
|
214
216
|
roles: string[];
|
|
215
217
|
users: { name: string; email: string }[];
|
|
218
|
+
publicVia?: string[];
|
|
216
219
|
}>;
|
|
217
220
|
|
|
218
221
|
/**
|
|
@@ -323,9 +326,17 @@ export interface IAccessControl {
|
|
|
323
326
|
* (named). The login-only `users` table is unioned in by the caller — this
|
|
324
327
|
* method covers the KB-canonical people the users table misses.
|
|
325
328
|
*/
|
|
326
|
-
kbPrincipals(
|
|
327
|
-
|
|
328
|
-
|
|
329
|
+
kbPrincipals(workspaceId: string): Promise<{
|
|
330
|
+
roles: string[];
|
|
331
|
+
groups: string[];
|
|
332
|
+
/**
|
|
333
|
+
* Plugins whose `plugin/<Name>/<verb>` principals exist (personal folders
|
|
334
|
+
* excluded): the display name and the repo-relative folder, so a caller
|
|
335
|
+
* can apply the discoverability verdict (`canRead` on `<folder>/access.md`).
|
|
336
|
+
*/
|
|
337
|
+
plugins: { name: string; folder: string }[];
|
|
338
|
+
people: { name: string; email: string }[];
|
|
339
|
+
}>;
|
|
329
340
|
|
|
330
341
|
/**
|
|
331
342
|
* Reverse-lookup an email by its SHA-256 hash (per `hashEmail` semantics)
|