@bevel-software/platform-core-backend 0.14.0 → 0.19.0
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 +8 -8
- package/dist/core/catalog-cache-invalidation.d.ts +45 -0
- package/dist/core/catalog-cache-invalidation.d.ts.map +1 -0
- package/dist/core/catalog-cache-invalidation.js +76 -0
- package/dist/core/catalog-cache-invalidation.js.map +1 -0
- package/dist/core/catalog-revision.d.ts +39 -0
- package/dist/core/catalog-revision.d.ts.map +1 -0
- package/dist/core/catalog-revision.js +94 -0
- package/dist/core/catalog-revision.js.map +1 -0
- package/dist/core/create-core-server.d.ts +7 -0
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +279 -64
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +66 -2
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +290 -111
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +138 -0
- package/dist/core/lifecycle.d.ts.map +1 -0
- package/dist/core/lifecycle.js +218 -0
- package/dist/core/lifecycle.js.map +1 -0
- package/dist/core/public-config.d.ts +82 -0
- package/dist/core/public-config.d.ts.map +1 -0
- package/dist/core/public-config.js +78 -0
- package/dist/core/public-config.js.map +1 -0
- package/dist/core/readiness.d.ts +86 -0
- package/dist/core/readiness.d.ts.map +1 -0
- package/dist/core/readiness.js +56 -0
- package/dist/core/readiness.js.map +1 -0
- package/dist/core-config.d.ts +30 -0
- package/dist/core-config.d.ts.map +1 -1
- package/dist/core-config.js +64 -2
- package/dist/core-config.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +231 -15
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +83 -12
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +815 -212
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access-mutation.service.d.ts +71 -5
- package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
- package/dist/modules/access/access-mutation.service.js +161 -13
- package/dist/modules/access/access-mutation.service.js.map +1 -1
- package/dist/modules/access/access-view.d.ts +147 -0
- package/dist/modules/access/access-view.d.ts.map +1 -0
- package/dist/modules/access/access-view.js +211 -0
- package/dist/modules/access/access-view.js.map +1 -0
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +312 -84
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/admin-locked-commit.d.ts +2 -3
- package/dist/modules/access/admin-locked-commit.d.ts.map +1 -1
- package/dist/modules/access/admin-locked-commit.js +9 -8
- package/dist/modules/access/admin-locked-commit.js.map +1 -1
- package/dist/modules/access/admin-route-helpers.d.ts.map +1 -1
- package/dist/modules/access/admin-route-helpers.js +4 -4
- package/dist/modules/access/admin-route-helpers.js.map +1 -1
- package/dist/modules/access/change-read-gate.d.ts +53 -0
- package/dist/modules/access/change-read-gate.d.ts.map +1 -0
- package/dist/modules/access/change-read-gate.js +134 -0
- package/dist/modules/access/change-read-gate.js.map +1 -0
- package/dist/modules/access/creator-access.d.ts +33 -39
- package/dist/modules/access/creator-access.d.ts.map +1 -1
- package/dist/modules/access/creator-access.js +92 -119
- package/dist/modules/access/creator-access.js.map +1 -1
- package/dist/modules/access/directory-sync-bot.d.ts.map +1 -1
- package/dist/modules/access/directory-sync-bot.js +3 -1
- package/dist/modules/access/directory-sync-bot.js.map +1 -1
- package/dist/modules/access/groups-admin.service.d.ts +3 -3
- package/dist/modules/access/groups-admin.service.js +2 -2
- package/dist/modules/access/reference-scan.d.ts +1 -1
- package/dist/modules/access/reference-scan.d.ts.map +1 -1
- package/dist/modules/access/reference-scan.js +5 -4
- package/dist/modules/access/reference-scan.js.map +1 -1
- package/dist/modules/access/roles-admin.service.d.ts +72 -13
- package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
- package/dist/modules/access/roles-admin.service.js +71 -13
- package/dist/modules/access/roles-admin.service.js.map +1 -1
- package/dist/modules/access/synced-groups-committer.d.ts.map +1 -1
- package/dist/modules/access/synced-groups-committer.js +8 -6
- package/dist/modules/access/synced-groups-committer.js.map +1 -1
- package/dist/modules/access/synced-groups-writer.d.ts.map +1 -1
- package/dist/modules/access/synced-groups-writer.js +3 -8
- package/dist/modules/access/synced-groups-writer.js.map +1 -1
- package/dist/modules/access/user-access-removal.service.d.ts +209 -0
- package/dist/modules/access/user-access-removal.service.d.ts.map +1 -0
- package/dist/modules/access/user-access-removal.service.js +662 -0
- package/dist/modules/access/user-access-removal.service.js.map +1 -0
- package/dist/modules/access-model/access-errors.d.ts +32 -1
- package/dist/modules/access-model/access-errors.d.ts.map +1 -1
- package/dist/modules/access-model/access-errors.js +38 -11
- package/dist/modules/access-model/access-errors.js.map +1 -1
- package/dist/modules/access-model/access-grammar.d.ts +100 -25
- package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
- package/dist/modules/access-model/access-grammar.js +162 -48
- package/dist/modules/access-model/access-grammar.js.map +1 -1
- package/dist/modules/access-model/access-splice.d.ts.map +1 -1
- package/dist/modules/access-model/access-splice.js +18 -18
- package/dist/modules/access-model/access-splice.js.map +1 -1
- package/dist/modules/access-model/access-template.d.ts +22 -0
- package/dist/modules/access-model/access-template.d.ts.map +1 -0
- package/dist/modules/access-model/access-template.js +42 -0
- package/dist/modules/access-model/access-template.js.map +1 -0
- package/dist/modules/access-model/change-gate.d.ts +65 -0
- package/dist/modules/access-model/change-gate.d.ts.map +1 -0
- package/dist/modules/access-model/change-gate.js +25 -0
- package/dist/modules/access-model/change-gate.js.map +1 -0
- package/dist/modules/access-model/creator.d.ts +13 -16
- package/dist/modules/access-model/creator.d.ts.map +1 -1
- package/dist/modules/access-model/creator.js.map +1 -1
- package/dist/modules/access-model/frontmatter-lines.d.ts +27 -0
- package/dist/modules/access-model/frontmatter-lines.d.ts.map +1 -0
- package/dist/modules/access-model/frontmatter-lines.js +74 -0
- package/dist/modules/access-model/frontmatter-lines.js.map +1 -0
- 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 +51 -0
- package/dist/modules/access-model/plugin-principals.d.ts.map +1 -0
- package/dist/modules/access-model/plugin-principals.js +119 -0
- package/dist/modules/access-model/plugin-principals.js.map +1 -0
- package/dist/modules/access-model/roles-yaml-guard.d.ts +73 -2
- package/dist/modules/access-model/roles-yaml-guard.d.ts.map +1 -1
- package/dist/modules/access-model/roles-yaml-guard.js +153 -3
- package/dist/modules/access-model/roles-yaml-guard.js.map +1 -1
- package/dist/modules/admin/admin-access.service.d.ts.map +1 -1
- package/dist/modules/admin/admin-access.service.js +5 -2
- package/dist/modules/admin/admin-access.service.js.map +1 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +21 -0
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -0
- package/dist/modules/agent-instructions/agent-instructions.routes.js +38 -0
- package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -0
- package/dist/modules/agent-instructions/compose.d.ts +71 -0
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -0
- package/dist/modules/agent-instructions/compose.js +234 -0
- package/dist/modules/agent-instructions/compose.js.map +1 -0
- package/dist/modules/agent-instructions/index.d.ts +4 -0
- package/dist/modules/agent-instructions/index.d.ts.map +1 -0
- package/dist/modules/agent-instructions/index.js +4 -0
- package/dist/modules/agent-instructions/index.js.map +1 -0
- package/dist/modules/agent-instructions/read-preamble.d.ts +37 -0
- package/dist/modules/agent-instructions/read-preamble.d.ts.map +1 -0
- package/dist/modules/agent-instructions/read-preamble.js +81 -0
- package/dist/modules/agent-instructions/read-preamble.js.map +1 -0
- package/dist/modules/auth/account-erasure.service.d.ts +30 -4
- package/dist/modules/auth/account-erasure.service.d.ts.map +1 -1
- package/dist/modules/auth/account-erasure.service.js +96 -10
- package/dist/modules/auth/account-erasure.service.js.map +1 -1
- package/dist/modules/auth/account.routes.d.ts +6 -3
- package/dist/modules/auth/account.routes.d.ts.map +1 -1
- package/dist/modules/auth/account.routes.js +109 -10
- package/dist/modules/auth/account.routes.js.map +1 -1
- package/dist/modules/auth/auth.routes.d.ts +10 -3
- package/dist/modules/auth/auth.routes.d.ts.map +1 -1
- package/dist/modules/auth/auth.routes.js +9 -4
- package/dist/modules/auth/auth.routes.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +89 -5
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +160 -25
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.d.ts +46 -3
- package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.js +99 -21
- package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.js +14 -2
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.d.ts.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.js +56 -7
- package/dist/modules/connection-probe/connection-probe.service.js.map +1 -1
- package/dist/modules/database/advisory-lock.d.ts +103 -0
- package/dist/modules/database/advisory-lock.d.ts.map +1 -0
- package/dist/modules/database/advisory-lock.js +320 -0
- package/dist/modules/database/advisory-lock.js.map +1 -0
- package/dist/modules/database/connection.d.ts.map +1 -1
- package/dist/modules/database/connection.js +9 -1
- package/dist/modules/database/connection.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +672 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +171 -0
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/database/migrate.d.ts.map +1 -1
- package/dist/modules/database/migrate.js +26 -6
- package/dist/modules/database/migrate.js.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.d.ts.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.js +4 -2
- package/dist/modules/declared-variables/declared-variables.routes.js.map +1 -1
- package/dist/modules/diff/diff.routes.d.ts.map +1 -1
- package/dist/modules/diff/diff.routes.js +2 -4
- package/dist/modules/diff/diff.routes.js.map +1 -1
- package/dist/modules/diff/diff.service.d.ts +4 -1
- package/dist/modules/diff/diff.service.d.ts.map +1 -1
- package/dist/modules/diff/diff.service.js +79 -81
- package/dist/modules/diff/diff.service.js.map +1 -1
- package/dist/modules/kb-fs/bevel-ignore.d.ts +35 -0
- package/dist/modules/kb-fs/bevel-ignore.d.ts.map +1 -0
- package/dist/modules/kb-fs/bevel-ignore.js +71 -0
- package/dist/modules/kb-fs/bevel-ignore.js.map +1 -0
- package/dist/modules/kb-fs/file-change-notifier.d.ts.map +1 -1
- package/dist/modules/kb-fs/file-change-notifier.js +3 -1
- package/dist/modules/kb-fs/file-change-notifier.js.map +1 -1
- package/dist/modules/kb-fs/git-guarded-filesystem.d.ts +37 -0
- package/dist/modules/kb-fs/git-guarded-filesystem.d.ts.map +1 -0
- package/dist/modules/kb-fs/git-guarded-filesystem.js +104 -0
- package/dist/modules/kb-fs/git-guarded-filesystem.js.map +1 -0
- package/dist/modules/kb-fs/locking-filesystem.d.ts +113 -18
- package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.js +353 -76
- package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
- package/dist/modules/kb-fs/node-fs.d.ts +23 -0
- package/dist/modules/kb-fs/node-fs.d.ts.map +1 -0
- package/dist/modules/kb-fs/node-fs.js +158 -0
- package/dist/modules/kb-fs/node-fs.js.map +1 -0
- package/dist/modules/kb-fs/read-only-filesystem.d.ts +10 -9
- package/dist/modules/kb-fs/read-only-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/read-only-filesystem.js +18 -9
- package/dist/modules/kb-fs/read-only-filesystem.js.map +1 -1
- package/dist/modules/kb-fs/repo-path.d.ts +100 -3
- package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
- package/dist/modules/kb-fs/repo-path.js +195 -3
- package/dist/modules/kb-fs/repo-path.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 +137 -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 +185 -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 +192 -0
- package/dist/modules/marketplace/git-http.routes.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts +33 -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 +88 -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 +126 -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 +192 -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 +252 -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 +121 -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 +213 -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 +135 -0
- package/dist/modules/marketplace/marketplace-repo.service.d.ts.map +1 -0
- package/dist/modules/marketplace/marketplace-repo.service.js +318 -0
- package/dist/modules/marketplace/marketplace-repo.service.js.map +1 -0
- package/dist/modules/mcp/downstream-pool.d.ts +107 -0
- package/dist/modules/mcp/downstream-pool.d.ts.map +1 -0
- package/dist/modules/mcp/downstream-pool.js +221 -0
- package/dist/modules/mcp/downstream-pool.js.map +1 -0
- package/dist/modules/mcp/downstream-token-refresh.d.ts +77 -0
- package/dist/modules/mcp/downstream-token-refresh.d.ts.map +1 -0
- package/dist/modules/mcp/downstream-token-refresh.js +166 -0
- package/dist/modules/mcp/downstream-token-refresh.js.map +1 -0
- package/dist/modules/mcp/first-call-probe.cli-args.d.ts +78 -0
- package/dist/modules/mcp/first-call-probe.cli-args.d.ts.map +1 -0
- package/dist/modules/mcp/first-call-probe.cli-args.js +151 -0
- package/dist/modules/mcp/first-call-probe.cli-args.js.map +1 -0
- package/dist/modules/mcp/first-call-probe.cli.d.ts +2 -0
- package/dist/modules/mcp/first-call-probe.cli.d.ts.map +1 -0
- package/dist/modules/mcp/first-call-probe.cli.js +62 -0
- package/dist/modules/mcp/first-call-probe.cli.js.map +1 -0
- package/dist/modules/mcp/first-call-probe.d.ts +165 -0
- package/dist/modules/mcp/first-call-probe.d.ts.map +1 -0
- package/dist/modules/mcp/first-call-probe.js +298 -0
- package/dist/modules/mcp/first-call-probe.js.map +1 -0
- package/dist/modules/mcp/manual-failure-memo.d.ts +42 -17
- package/dist/modules/mcp/manual-failure-memo.d.ts.map +1 -1
- package/dist/modules/mcp/manual-failure-memo.js +79 -23
- package/dist/modules/mcp/manual-failure-memo.js.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.d.ts +9 -5
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +24 -16
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/mcp.routes.d.ts +4 -4
- package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.routes.js +83 -179
- package/dist/modules/mcp/mcp.routes.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +218 -61
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +713 -159
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +8 -2
- 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 +16 -2
- 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/mcp/surface-log-throttle.d.ts +59 -0
- package/dist/modules/mcp/surface-log-throttle.d.ts.map +1 -0
- package/dist/modules/mcp/surface-log-throttle.js +94 -0
- package/dist/modules/mcp/surface-log-throttle.js.map +1 -0
- package/dist/modules/plugins/compile/compile-marketplace.d.ts +104 -0
- package/dist/modules/plugins/compile/compile-marketplace.d.ts.map +1 -0
- package/dist/modules/plugins/compile/compile-marketplace.js +323 -0
- package/dist/modules/plugins/compile/compile-marketplace.js.map +1 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts +73 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts.map +1 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.js +135 -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 +158 -0
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js.map +1 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts +65 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.js +204 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.js.map +1 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.d.ts +39 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.js +110 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.js.map +1 -0
- package/dist/modules/plugins/discovery/native.source.d.ts +16 -0
- package/dist/modules/plugins/discovery/native.source.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/native.source.js +120 -0
- package/dist/modules/plugins/discovery/native.source.js.map +1 -0
- package/dist/modules/plugins/discovery/plugin-source.d.ts +110 -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 +15 -3
- package/dist/modules/plugins/index.d.ts.map +1 -1
- package/dist/modules/plugins/index.js +13 -2
- package/dist/modules/plugins/index.js.map +1 -1
- package/dist/modules/plugins/join-request-jobs.service.d.ts +250 -0
- package/dist/modules/plugins/join-request-jobs.service.d.ts.map +1 -0
- package/dist/modules/plugins/join-request-jobs.service.js +602 -0
- package/dist/modules/plugins/join-request-jobs.service.js.map +1 -0
- package/dist/modules/plugins/join-request-records.store.d.ts +161 -0
- package/dist/modules/plugins/join-request-records.store.d.ts.map +1 -0
- package/dist/modules/plugins/join-request-records.store.js +179 -0
- package/dist/modules/plugins/join-request-records.store.js.map +1 -0
- package/dist/modules/plugins/join-requests.service.d.ts.map +1 -1
- package/dist/modules/plugins/join-requests.service.js +4 -2
- package/dist/modules/plugins/join-requests.service.js.map +1 -1
- package/dist/modules/plugins/plugin-links.d.ts +69 -0
- package/dist/modules/plugins/plugin-links.d.ts.map +1 -0
- package/dist/modules/plugins/plugin-links.js +163 -0
- package/dist/modules/plugins/plugin-links.js.map +1 -0
- package/dist/modules/plugins/plugin-links.service.d.ts +111 -0
- package/dist/modules/plugins/plugin-links.service.d.ts.map +1 -0
- package/dist/modules/plugins/plugin-links.service.js +295 -0
- package/dist/modules/plugins/plugin-links.service.js.map +1 -0
- package/dist/modules/plugins/plugin-provision.service.d.ts +129 -21
- package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.js +320 -75
- package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
- package/dist/modules/plugins/plugin-rename.service.d.ts +71 -0
- package/dist/modules/plugins/plugin-rename.service.d.ts.map +1 -0
- package/dist/modules/plugins/plugin-rename.service.js +254 -0
- package/dist/modules/plugins/plugin-rename.service.js.map +1 -0
- package/dist/modules/plugins/plugins.contract.d.ts +78 -4
- package/dist/modules/plugins/plugins.contract.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +44 -5
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +297 -115
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/plugins/plugins.service.d.ts +82 -3
- package/dist/modules/plugins/plugins.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.service.js +178 -48
- package/dist/modules/plugins/plugins.service.js.map +1 -1
- package/dist/modules/plugins/plugins.tools.d.ts +25 -0
- package/dist/modules/plugins/plugins.tools.d.ts.map +1 -0
- package/dist/modules/plugins/plugins.tools.js +82 -0
- package/dist/modules/plugins/plugins.tools.js.map +1 -0
- package/dist/modules/plugins/teams.routes.d.ts +42 -0
- package/dist/modules/plugins/teams.routes.d.ts.map +1 -0
- package/dist/modules/plugins/teams.routes.js +79 -0
- package/dist/modules/plugins/teams.routes.js.map +1 -0
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +40 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.js +202 -37
- package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
- package/dist/modules/secrets-vault/index.d.ts +1 -1
- package/dist/modules/secrets-vault/index.d.ts.map +1 -1
- package/dist/modules/secrets-vault/index.js.map +1 -1
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.d.ts.map +1 -1
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js +5 -3
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-variable-loader.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-variable-loader.js +3 -1
- package/dist/modules/secrets-vault/secrets-variable-loader.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +78 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.js +40 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.js +106 -52
- package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
- package/dist/modules/settings/connection-check.d.ts +118 -0
- package/dist/modules/settings/connection-check.d.ts.map +1 -0
- package/dist/modules/settings/connection-check.js +287 -0
- package/dist/modules/settings/connection-check.js.map +1 -0
- package/dist/modules/settings/deployment-settings.service.d.ts +157 -2
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +412 -35
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/settings/git-root-folders.d.ts +54 -0
- package/dist/modules/settings/git-root-folders.d.ts.map +1 -0
- package/dist/modules/settings/git-root-folders.js +130 -0
- package/dist/modules/settings/git-root-folders.js.map +1 -0
- package/dist/modules/settings/oidc-check.d.ts +85 -0
- package/dist/modules/settings/oidc-check.d.ts.map +1 -0
- package/dist/modules/settings/oidc-check.js +172 -0
- package/dist/modules/settings/oidc-check.js.map +1 -0
- package/dist/modules/settings/setup.routes.d.ts +57 -1
- package/dist/modules/settings/setup.routes.d.ts.map +1 -1
- package/dist/modules/settings/setup.routes.js +408 -108
- package/dist/modules/settings/setup.routes.js.map +1 -1
- package/dist/modules/skills/allowed-tools-check.d.ts +132 -0
- package/dist/modules/skills/allowed-tools-check.d.ts.map +1 -0
- package/dist/modules/skills/allowed-tools-check.js +260 -0
- package/dist/modules/skills/allowed-tools-check.js.map +1 -0
- package/dist/modules/skills/index.d.ts +4 -1
- package/dist/modules/skills/index.d.ts.map +1 -1
- package/dist/modules/skills/index.js +2 -0
- package/dist/modules/skills/index.js.map +1 -1
- package/dist/modules/skills/pending-skills.service.d.ts +6 -11
- package/dist/modules/skills/pending-skills.service.d.ts.map +1 -1
- package/dist/modules/skills/pending-skills.service.js +24 -71
- 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 +171 -0
- package/dist/modules/skills/skill-access-requests.routes.js.map +1 -0
- package/dist/modules/skills/skills.contract.d.ts +67 -2
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.d.ts +35 -3
- package/dist/modules/skills/skills.routes.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.js +57 -4
- package/dist/modules/skills/skills.routes.js.map +1 -1
- package/dist/modules/skills/skills.service.d.ts +31 -3
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +236 -65
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts +4 -1
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +41 -6
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-auth/connection-key-rejection.d.ts +18 -0
- package/dist/modules/tool-auth/connection-key-rejection.d.ts.map +1 -0
- package/dist/modules/tool-auth/connection-key-rejection.js +20 -0
- package/dist/modules/tool-auth/connection-key-rejection.js.map +1 -0
- package/dist/modules/tool-auth/connection-keys-admin.routes.d.ts +14 -0
- package/dist/modules/tool-auth/connection-keys-admin.routes.d.ts.map +1 -0
- package/dist/modules/tool-auth/connection-keys-admin.routes.js +67 -0
- package/dist/modules/tool-auth/connection-keys-admin.routes.js.map +1 -0
- package/dist/modules/tool-auth/external-api-key.interface.d.ts +66 -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 +23 -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 +79 -19
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.d.ts +5 -2
- package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.js +5 -2
- package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
- package/dist/modules/tool-auth/key-or-session.middleware.d.ts +23 -0
- package/dist/modules/tool-auth/key-or-session.middleware.d.ts.map +1 -0
- package/dist/modules/tool-auth/key-or-session.middleware.js +39 -0
- package/dist/modules/tool-auth/key-or-session.middleware.js.map +1 -0
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts +7 -1
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +8 -5
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/tool-context.d.ts +14 -0
- package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-context.js +28 -6
- package/dist/modules/tool-helpers/tool-context.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.js +22 -5
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts +6 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.js +7 -1
- package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
- package/dist/modules/tool-manuals/index.d.ts +2 -1
- package/dist/modules/tool-manuals/index.d.ts.map +1 -1
- package/dist/modules/tool-manuals/index.js +1 -0
- package/dist/modules/tool-manuals/index.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 +136 -96
- package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts +10 -2
- package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.js +12 -15
- package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -1
- package/dist/modules/tool-manuals/pending-tools.service.d.ts +50 -0
- package/dist/modules/tool-manuals/pending-tools.service.d.ts.map +1 -0
- package/dist/modules/tool-manuals/pending-tools.service.js +149 -0
- package/dist/modules/tool-manuals/pending-tools.service.js.map +1 -0
- package/dist/modules/tool-manuals/tool-delete.service.d.ts +135 -0
- package/dist/modules/tool-manuals/tool-delete.service.d.ts.map +1 -0
- package/dist/modules/tool-manuals/tool-delete.service.js +303 -0
- package/dist/modules/tool-manuals/tool-delete.service.js.map +1 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +161 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.js +9 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +19 -2
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.js +127 -57
- package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +121 -2
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +415 -92
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +78 -6
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +88 -54
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/event-bus.d.ts +10 -4
- package/dist/modules/workflow/event-bus.d.ts.map +1 -1
- package/dist/modules/workflow/event-bus.js +14 -11
- package/dist/modules/workflow/event-bus.js.map +1 -1
- package/dist/modules/workflow/events.routes.d.ts +17 -2
- package/dist/modules/workflow/events.routes.d.ts.map +1 -1
- package/dist/modules/workflow/events.routes.js +31 -12
- package/dist/modules/workflow/events.routes.js.map +1 -1
- package/dist/modules/workflow/file-lock.service.d.ts +22 -4
- package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
- package/dist/modules/workflow/file-lock.service.js +27 -4
- package/dist/modules/workflow/file-lock.service.js.map +1 -1
- package/dist/modules/workflow/git/change-request-link.d.ts +20 -0
- package/dist/modules/workflow/git/change-request-link.d.ts.map +1 -0
- package/dist/modules/workflow/git/change-request-link.js +34 -0
- package/dist/modules/workflow/git/change-request-link.js.map +1 -0
- package/dist/modules/workflow/git/git-version.d.ts.map +1 -1
- package/dist/modules/workflow/git/git-version.js +3 -1
- package/dist/modules/workflow/git/git-version.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +303 -6
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +753 -170
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/node-git-runner.d.ts +36 -0
- package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -0
- package/dist/modules/workflow/git/node-git-runner.js +273 -0
- package/dist/modules/workflow/git/node-git-runner.js.map +1 -0
- package/dist/modules/workflow/git/pull-request.service.d.ts +57 -2
- package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.js +165 -25
- package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.service.d.ts +11 -0
- package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.service.js +24 -4
- package/dist/modules/workflow/pending-commits.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.d.ts +15 -8
- package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.js +107 -58
- package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
- package/dist/modules/workflow/recovery-bot.d.ts.map +1 -1
- package/dist/modules/workflow/recovery-bot.js +3 -1
- package/dist/modules/workflow/recovery-bot.js.map +1 -1
- package/dist/modules/workflow/review-workflow/approval-lock.d.ts +60 -0
- package/dist/modules/workflow/review-workflow/approval-lock.d.ts.map +1 -0
- package/dist/modules/workflow/review-workflow/approval-lock.js +116 -0
- package/dist/modules/workflow/review-workflow/approval-lock.js.map +1 -0
- package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts +54 -7
- package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts +64 -0
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js +269 -90
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
- package/dist/modules/workflow/sanitize-error.d.ts +5 -2
- package/dist/modules/workflow/sanitize-error.d.ts.map +1 -1
- package/dist/modules/workflow/sanitize-error.js +8 -4
- package/dist/modules/workflow/sanitize-error.js.map +1 -1
- package/dist/modules/workflow/workflow.routes.d.ts +2 -0
- package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.routes.js +259 -29
- package/dist/modules/workflow/workflow.routes.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +217 -14
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +1142 -109
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/empty-dirs.d.ts +15 -0
- package/dist/modules/workspace/empty-dirs.d.ts.map +1 -0
- package/dist/modules/workspace/empty-dirs.js +90 -0
- package/dist/modules/workspace/empty-dirs.js.map +1 -0
- package/dist/modules/workspace/file-readers/content-mode.d.ts +50 -0
- package/dist/modules/workspace/file-readers/content-mode.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/content-mode.js +55 -0
- package/dist/modules/workspace/file-readers/content-mode.js.map +1 -0
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts +4 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.js +4 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.js.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.types.d.ts +5 -2
- package/dist/modules/workspace/file-readers/doc-extract.types.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.types.js +7 -4
- package/dist/modules/workspace/file-readers/doc-extract.types.js.map +1 -1
- package/dist/modules/workspace/file-readers/document-reader.d.ts +2 -0
- package/dist/modules/workspace/file-readers/document-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/document-reader.js +5 -0
- package/dist/modules/workspace/file-readers/document-reader.js.map +1 -1
- package/dist/modules/workspace/file-readers/extract-docx.d.ts +6 -0
- package/dist/modules/workspace/file-readers/extract-docx.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/extract-docx.js +6 -0
- package/dist/modules/workspace/file-readers/extract-docx.js.map +1 -1
- package/dist/modules/workspace/file-readers/file-reader.d.ts +24 -5
- package/dist/modules/workspace/file-readers/file-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/file-reader.js +11 -6
- package/dist/modules/workspace/file-readers/file-reader.js.map +1 -1
- package/dist/modules/workspace/file-readers/file-reader.registry.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/file-reader.registry.js +6 -1
- package/dist/modules/workspace/file-readers/file-reader.registry.js.map +1 -1
- package/dist/modules/workspace/file-readers/image-reader.d.ts +7 -5
- package/dist/modules/workspace/file-readers/image-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/image-reader.js +9 -5
- package/dist/modules/workspace/file-readers/image-reader.js.map +1 -1
- package/dist/modules/workspace/file-readers/ooxml-text.d.ts +3 -0
- package/dist/modules/workspace/file-readers/ooxml-text.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/ooxml-text.js +39 -2
- package/dist/modules/workspace/file-readers/ooxml-text.js.map +1 -1
- package/dist/modules/workspace/file-readers/text-reader.d.ts +29 -1
- package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/text-reader.js +57 -3
- package/dist/modules/workspace/file-readers/text-reader.js.map +1 -1
- package/dist/modules/workspace/git-internals.middleware.d.ts +28 -0
- package/dist/modules/workspace/git-internals.middleware.d.ts.map +1 -0
- package/dist/modules/workspace/git-internals.middleware.js +93 -0
- package/dist/modules/workspace/git-internals.middleware.js.map +1 -0
- package/dist/modules/workspace/not-found.d.ts +80 -0
- package/dist/modules/workspace/not-found.d.ts.map +1 -0
- package/dist/modules/workspace/not-found.js +112 -0
- package/dist/modules/workspace/not-found.js.map +1 -0
- package/dist/modules/workspace/session-ontology.gate.d.ts +1 -1
- package/dist/modules/workspace/session-ontology.gate.d.ts.map +1 -1
- package/dist/modules/workspace/session-ontology.gate.js +1 -1
- package/dist/modules/workspace/session-ontology.gate.js.map +1 -1
- package/dist/modules/workspace/startup/beside-checkout.d.ts +42 -0
- package/dist/modules/workspace/startup/beside-checkout.d.ts.map +1 -0
- package/dist/modules/workspace/startup/beside-checkout.js +110 -0
- package/dist/modules/workspace/startup/beside-checkout.js.map +1 -0
- package/dist/modules/workspace/startup/kb-git.d.ts +7 -12
- package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-git.js +34 -35
- package/dist/modules/workspace/startup/kb-git.js.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts +95 -0
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.js +257 -55
- package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts +45 -0
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js +417 -385
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/personal-spaces.step.d.ts +48 -0
- package/dist/modules/workspace/startup/steps/personal-spaces.step.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/personal-spaces.step.js +183 -0
- package/dist/modules/workspace/startup/steps/personal-spaces.step.js.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.d.ts +51 -0
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.js +158 -0
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.js.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-layout.d.ts +64 -0
- package/dist/modules/workspace/startup/steps/plugin-layout.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-layout.js +97 -0
- package/dist/modules/workspace/startup/steps/plugin-layout.js.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts +41 -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 +85 -0
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.js.map +1 -0
- package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts +3 -1
- package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/roles-yaml.step.js +4 -12
- package/dist/modules/workspace/startup/steps/roles-yaml.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts +6 -1
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js +174 -54
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts +96 -24
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.js +521 -126
- package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.d.ts +94 -0
- package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/template-source.js +160 -0
- package/dist/modules/workspace/startup/steps/template-source.js.map +1 -0
- package/dist/modules/workspace/workspace.routes.d.ts +14 -2
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +657 -246
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +323 -12
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +1006 -257
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +42 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +1590 -146
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/modules/workspace/write-denial.d.ts +76 -0
- package/dist/modules/workspace/write-denial.d.ts.map +1 -0
- package/dist/modules/workspace/write-denial.js +138 -0
- package/dist/modules/workspace/write-denial.js.map +1 -0
- package/dist/shared/canonical-file-identity.d.ts +64 -0
- package/dist/shared/canonical-file-identity.d.ts.map +1 -0
- package/dist/shared/canonical-file-identity.js +92 -0
- package/dist/shared/canonical-file-identity.js.map +1 -0
- package/dist/shared/domain-errors.d.ts +128 -0
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +166 -0
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/email-identity.d.ts +32 -0
- package/dist/shared/email-identity.d.ts.map +1 -0
- package/dist/shared/email-identity.js +37 -0
- package/dist/shared/email-identity.js.map +1 -0
- 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/fs.contract.d.ts +206 -0
- package/dist/shared/fs.contract.d.ts.map +1 -0
- package/dist/shared/fs.contract.js +79 -0
- package/dist/shared/fs.contract.js.map +1 -0
- package/dist/shared/git-failure.d.ts +80 -0
- package/dist/shared/git-failure.d.ts.map +1 -0
- package/dist/shared/git-failure.js +139 -0
- package/dist/shared/git-failure.js.map +1 -0
- package/dist/shared/git-internals.d.ts +17 -0
- package/dist/shared/git-internals.d.ts.map +1 -0
- package/dist/shared/git-internals.js +123 -0
- package/dist/shared/git-internals.js.map +1 -0
- package/dist/shared/git.contract.d.ts +157 -0
- package/dist/shared/git.contract.d.ts.map +1 -0
- package/dist/shared/git.contract.js +103 -0
- package/dist/shared/git.contract.js.map +1 -0
- package/dist/shared/http-errors.d.ts +33 -0
- package/dist/shared/http-errors.d.ts.map +1 -0
- package/dist/shared/http-errors.js +35 -0
- package/dist/shared/http-errors.js.map +1 -0
- 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/logger.contract.d.ts +63 -0
- package/dist/shared/logger.contract.d.ts.map +1 -0
- package/dist/shared/logger.contract.js +126 -0
- package/dist/shared/logger.contract.js.map +1 -0
- package/dist/shared/logging.d.ts +31 -0
- package/dist/shared/logging.d.ts.map +1 -0
- package/dist/shared/logging.js +92 -0
- package/dist/shared/logging.js.map +1 -0
- package/dist/shared/path-containment.d.ts +21 -0
- package/dist/shared/path-containment.d.ts.map +1 -0
- package/dist/shared/path-containment.js +34 -0
- package/dist/shared/path-containment.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/pending-proposals.d.ts +71 -0
- package/dist/shared/pending-proposals.d.ts.map +1 -0
- package/dist/shared/pending-proposals.js +98 -0
- package/dist/shared/pending-proposals.js.map +1 -0
- package/dist/shared/printable.d.ts +22 -0
- package/dist/shared/printable.d.ts.map +1 -0
- package/dist/shared/printable.js +65 -0
- package/dist/shared/printable.js.map +1 -0
- package/dist/shared/redact-secret.d.ts +30 -0
- package/dist/shared/redact-secret.d.ts.map +1 -0
- package/dist/shared/redact-secret.js +114 -0
- package/dist/shared/redact-secret.js.map +1 -0
- package/dist/shared/rename-no-replace.d.ts +119 -0
- package/dist/shared/rename-no-replace.d.ts.map +1 -0
- package/dist/shared/rename-no-replace.js +323 -0
- package/dist/shared/rename-no-replace.js.map +1 -0
- package/dist/shared/ssrf.d.ts.map +1 -1
- package/dist/shared/ssrf.js +49 -18
- package/dist/shared/ssrf.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/dist/version.d.ts +10 -6
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +10 -6
- package/dist/version.js.map +1 -1
- package/kb-template/.bevelignore +4 -3
- package/kb-template/AGENTS.md +364 -54
- package/kb-template/KnowledgeBase/How to get started.md +1 -1
- package/kb-template/Skills/.gitkeep +0 -0
- package/kb-template/access.md +5 -3
- package/kb-template/mcp-description.md +27 -0
- package/migrations/0005_github_facade.sql +25 -0
- package/migrations/0006_api_tokens_revoked_by.sql +1 -0
- package/migrations/0007_github_facade_registered.sql +1 -0
- package/migrations/0008_change_request_apply_failure.sql +4 -0
- package/migrations/0009_change_request_apply_failure_kind.sql +13 -0
- package/migrations/0010_plugin_join_requests.sql +15 -0
- package/migrations/0011_plugin_join_request_claim.sql +11 -0
- package/migrations/meta/0005_snapshot.json +1717 -0
- package/migrations/meta/0006_snapshot.json +1723 -0
- package/migrations/meta/0007_snapshot.json +1729 -0
- package/migrations/meta/0008_snapshot.json +1753 -0
- package/migrations/meta/0009_snapshot.json +1759 -0
- package/migrations/meta/0010_snapshot.json +1872 -0
- package/migrations/meta/0011_snapshot.json +1884 -0
- package/migrations/meta/_journal.json +49 -0
- package/package.json +8 -7
- package/src/__tests__/core-config.domain.test.ts +67 -2
- package/src/__tests__/kb-layout-config.test.ts +295 -0
- package/src/__tests__/open-change-gate.ts +15 -0
- package/src/core/__tests__/catalog-cache-invalidation.test.ts +226 -0
- package/src/core/__tests__/catalog-revision.test.ts +190 -0
- package/src/core/__tests__/lifecycle.test.ts +369 -0
- package/src/core/__tests__/public-config.test.ts +30 -0
- package/src/core/__tests__/readiness.test.ts +109 -0
- package/src/core/catalog-cache-invalidation.ts +111 -0
- package/src/core/catalog-revision.ts +110 -0
- package/src/core/create-core-server.ts +358 -63
- package/src/core/create-core-services.ts +449 -107
- package/src/core/lifecycle.ts +312 -0
- package/src/core/public-config.ts +86 -0
- package/src/core/readiness.ts +123 -0
- package/src/core-config.ts +70 -4
- package/src/index.ts +15 -0
- package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +362 -0
- package/src/modules/access/__tests__/access-control.atref-batch.test.ts +2 -1
- package/src/modules/access/__tests__/access-control.atref-cache.test.ts +46 -22
- package/src/modules/access/__tests__/access-control.deny-dependents.test.ts +215 -0
- package/src/modules/access/__tests__/access-control.download-read.test.ts +268 -0
- package/src/modules/access/__tests__/access-control.everyone-read.test.ts +145 -0
- package/src/modules/access/__tests__/access-control.group-read.test.ts +163 -0
- package/src/modules/access/__tests__/access-control.invalidate-race.test.ts +88 -0
- package/src/modules/access/__tests__/access-control.platform-restore.test.ts +152 -0
- package/src/modules/access/__tests__/access-control.prospective.test.ts +189 -0
- package/src/modules/access/__tests__/access-control.read-batch.test.ts +2 -1
- package/src/modules/access/__tests__/access-control.service.test.ts +142 -81
- package/src/modules/access/__tests__/access-groups.test.ts +47 -14
- package/src/modules/access/__tests__/access-md-format.test.ts +2 -1
- package/src/modules/access/__tests__/access-mutation.service.test.ts +52 -4
- package/src/modules/access/__tests__/access-own-read-grant.test.ts +2 -0
- package/src/modules/access/__tests__/access-personal-plugin.test.ts +153 -0
- package/src/modules/access/__tests__/access-plugin-principals.test.ts +385 -0
- package/src/modules/access/__tests__/access-restrict-here.test.ts +450 -0
- package/src/modules/access/__tests__/access.routes.batch.test.ts +139 -0
- package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +394 -0
- package/src/modules/access/__tests__/access.routes.group-grant.test.ts +6 -2
- package/src/modules/access/__tests__/access.routes.overrides.test.ts +3 -2
- package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +234 -0
- package/src/modules/access/__tests__/access.routes.prospective.test.ts +216 -0
- package/src/modules/access/__tests__/access.routes.revoke.test.ts +7 -2
- package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +360 -0
- package/src/modules/access/__tests__/change-read-gate.test.ts +272 -0
- package/src/modules/access/__tests__/creator-access.test.ts +81 -83
- package/src/modules/access/__tests__/grant-sources.test.ts +3 -2
- package/src/modules/access/__tests__/groups-admin.service.test.ts +14 -5
- package/src/modules/access/__tests__/roles-admin.security.test.ts +196 -0
- package/src/modules/access/__tests__/roles-admin.service.test.ts +127 -2
- package/src/modules/access/__tests__/roles-capabilities.test.ts +5 -4
- package/src/modules/access/__tests__/roles.routes.test.ts +5 -2
- package/src/modules/access/__tests__/user-access-removal.service.test.ts +402 -0
- package/src/modules/access/__tests__/users-db-double.ts +63 -0
- package/src/modules/access/access-control.interface.ts +269 -18
- package/src/modules/access/access-control.service.ts +943 -226
- package/src/modules/access/access-mutation.service.ts +182 -13
- package/src/modules/access/access-view.ts +316 -0
- package/src/modules/access/access.routes.ts +385 -105
- package/src/modules/access/admin-locked-commit.ts +12 -14
- package/src/modules/access/admin-route-helpers.ts +4 -4
- package/src/modules/access/change-read-gate.ts +148 -0
- package/src/modules/access/creator-access.ts +89 -124
- package/src/modules/access/directory-sync-bot.ts +4 -1
- package/src/modules/access/groups-admin.service.ts +3 -3
- package/src/modules/access/reference-scan.ts +5 -5
- package/src/modules/access/roles-admin.service.ts +94 -15
- package/src/modules/access/synced-groups-committer.ts +10 -15
- package/src/modules/access/synced-groups-writer.ts +3 -12
- package/src/modules/access/user-access-removal.service.ts +711 -0
- package/src/modules/access-model/__tests__/access-grammar.test.ts +18 -0
- package/src/modules/access-model/__tests__/access-splice.test.ts +63 -1
- package/src/modules/access-model/__tests__/frontmatter-fences.test.ts +71 -0
- package/src/modules/access-model/__tests__/plugin-token-canonical.test.ts +40 -0
- package/src/modules/access-model/__tests__/roles-yaml-guard.test.ts +131 -0
- package/src/modules/access-model/access-errors.ts +56 -15
- package/src/modules/access-model/access-grammar.ts +198 -49
- package/src/modules/access-model/access-splice.ts +18 -18
- package/src/modules/access-model/access-template.ts +44 -0
- package/src/modules/access-model/change-gate.ts +80 -0
- package/src/modules/access-model/creator.ts +18 -21
- package/src/modules/access-model/frontmatter-lines.ts +98 -0
- package/src/modules/access-model/group-files.ts +4 -0
- package/src/modules/access-model/plugin-principals.ts +130 -0
- package/src/modules/access-model/roles-yaml-guard.ts +182 -4
- package/src/modules/admin/admin-access.service.ts +6 -2
- package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +100 -0
- package/src/modules/agent-instructions/__tests__/compose.test.ts +256 -0
- package/src/modules/agent-instructions/__tests__/read-preamble.test.ts +135 -0
- package/src/modules/agent-instructions/agent-instructions.routes.ts +41 -0
- package/src/modules/agent-instructions/compose.ts +271 -0
- package/src/modules/agent-instructions/index.ts +14 -0
- package/src/modules/agent-instructions/read-preamble.ts +88 -0
- package/src/modules/auth/__tests__/account-erasure.approval-gate.test.ts +209 -0
- package/src/modules/auth/__tests__/account.routes.test.ts +178 -2
- package/src/modules/auth/__tests__/auth.middleware.test.ts +78 -0
- package/src/modules/auth/__tests__/auth.service.test.ts +338 -5
- package/src/modules/auth/__tests__/oidc-auth-provider.test.ts +352 -12
- package/src/modules/auth/__tests__/onboarding-done.route.test.ts +3 -1
- package/src/modules/auth/account-erasure.service.ts +117 -10
- package/src/modules/auth/account.routes.ts +115 -14
- package/src/modules/auth/auth.routes.ts +20 -7
- package/src/modules/auth/auth.service.ts +187 -28
- package/src/modules/auth/oidc-auth-provider.ts +120 -22
- package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +109 -1
- package/src/modules/code-mode/code-mode.tool.ts +13 -2
- package/src/modules/connection-probe/__tests__/connection-probe.service.test.ts +78 -0
- package/src/modules/connection-probe/connection-probe.service.ts +59 -7
- package/src/modules/database/__tests__/advisory-lease.test.ts +152 -0
- package/src/modules/database/__tests__/advisory-lock.test.ts +147 -0
- package/src/modules/database/advisory-lock.ts +346 -0
- package/src/modules/database/connection.ts +9 -1
- package/src/modules/database/core-schema.ts +177 -0
- package/src/modules/database/migrate.ts +27 -6
- package/src/modules/declared-variables/declared-variables.routes.ts +6 -3
- package/src/modules/diff/__tests__/diff.service.seed-atomicity.test.ts +3 -0
- package/src/modules/diff/__tests__/diff.service.test.ts +3 -0
- package/src/modules/diff/diff.routes.ts +2 -4
- package/src/modules/diff/diff.service.ts +75 -79
- package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +472 -9
- package/src/modules/kb-fs/__tests__/node-fs.test.ts +409 -0
- package/src/modules/kb-fs/__tests__/repo-path.test.ts +210 -1
- package/src/modules/{workspace → kb-fs}/bevel-ignore.ts +22 -15
- package/src/modules/kb-fs/file-change-notifier.ts +4 -1
- package/src/modules/kb-fs/git-guarded-filesystem.ts +123 -0
- package/src/modules/kb-fs/locking-filesystem.ts +399 -109
- package/src/modules/kb-fs/node-fs.ts +165 -0
- package/src/modules/kb-fs/read-only-filesystem.ts +18 -9
- package/src/modules/kb-fs/repo-path.ts +213 -3
- 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 +200 -0
- package/src/modules/kb-sync/kb-sync.service.ts +204 -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 +575 -0
- package/src/modules/marketplace/__tests__/marketplace-git.test.ts +180 -0
- package/src/modules/marketplace/__tests__/marketplace-repo.head.test.ts +47 -0
- package/src/modules/marketplace/git-http.routes.ts +196 -0
- package/src/modules/marketplace/github-facade/github-facade-admin.routes.ts +110 -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 +263 -0
- package/src/modules/marketplace/github-facade/github-facade.routes.ts +294 -0
- package/src/modules/marketplace/github-facade/github-facade.service.ts +282 -0
- package/src/modules/marketplace/github-facade/index.ts +26 -0
- package/src/modules/marketplace/index.ts +3 -0
- package/src/modules/marketplace/marketplace-repo.service.ts +352 -0
- package/src/modules/mcp/__tests__/downstream-pool.test.ts +278 -0
- package/src/modules/mcp/__tests__/downstream-token-refresh.test.ts +178 -0
- package/src/modules/mcp/__tests__/fake-downstream-mcp-server.ts +156 -0
- package/src/modules/mcp/__tests__/first-call-probe.cli-args.test.ts +178 -0
- package/src/modules/mcp/__tests__/first-call-probe.test.ts +414 -0
- package/src/modules/mcp/__tests__/manual-failure-memo.test.ts +98 -6
- package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +8 -5
- package/src/modules/mcp/__tests__/mcp-routes-harness.ts +1 -3
- package/src/modules/mcp/__tests__/mcp.e2e.test.ts +1055 -59
- package/src/modules/mcp/__tests__/mcp.routes.stateless.test.ts +182 -0
- package/src/modules/mcp/__tests__/mcp.service.test.ts +274 -6
- package/src/modules/mcp/__tests__/surface-log-throttle.test.ts +169 -0
- package/src/modules/mcp/downstream-pool.ts +256 -0
- package/src/modules/mcp/downstream-token-refresh.ts +170 -0
- package/src/modules/mcp/first-call-probe.cli-args.ts +159 -0
- package/src/modules/mcp/first-call-probe.cli.ts +60 -0
- package/src/modules/mcp/first-call-probe.ts +418 -0
- package/src/modules/mcp/manual-failure-memo.ts +81 -23
- package/src/modules/mcp/mcp-auth.middleware.ts +25 -16
- package/src/modules/mcp/mcp.routes.ts +376 -482
- package/src/modules/mcp/mcp.service.ts +846 -176
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +8 -2
- package/src/modules/mcp/oauth/oauth-consent.routes.ts +28 -6
- package/src/modules/mcp/oauth/oauth-state.ts +15 -3
- package/src/modules/mcp/surface-log-throttle.ts +120 -0
- package/src/modules/plugins/__tests__/bundle-dialect.e2e.test.ts +166 -0
- package/src/modules/plugins/__tests__/bundle-dialect.test.ts +165 -0
- package/src/modules/plugins/__tests__/compile-marketplace.test.ts +423 -0
- package/src/modules/plugins/__tests__/fake-join-request-store.ts +189 -0
- package/src/modules/plugins/__tests__/join-request-jobs.service.test.ts +608 -0
- package/src/modules/plugins/__tests__/plugin-creation-display-name.test.ts +241 -0
- package/src/modules/plugins/__tests__/plugin-index.service.test.ts +201 -16
- package/src/modules/plugins/__tests__/plugin-links.service.test.ts +284 -0
- package/src/modules/plugins/__tests__/plugin-names.test.ts +77 -0
- package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +330 -20
- package/src/modules/plugins/__tests__/plugin-rename.service.test.ts +456 -0
- package/src/modules/plugins/__tests__/plugins.routes.test.ts +441 -43
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +131 -0
- package/src/modules/plugins/__tests__/teams.routes.test.ts +248 -0
- package/src/modules/plugins/compile/compile-marketplace.ts +500 -0
- package/src/modules/plugins/compile/marketplace-compiler.service.ts +151 -0
- package/src/modules/plugins/discovery/__tests__/kb-plugin-source.test.ts +365 -0
- package/src/modules/plugins/discovery/bundle-dialect/bundle.source.ts +161 -0
- package/src/modules/plugins/discovery/bundle-dialect/registry.ts +250 -0
- package/src/modules/plugins/discovery/kb-plugin-source.ts +117 -0
- package/src/modules/plugins/discovery/native.source.ts +140 -0
- package/src/modules/plugins/discovery/plugin-source.ts +113 -0
- package/src/modules/plugins/index.ts +25 -2
- package/src/modules/plugins/join-request-jobs.service.ts +675 -0
- package/src/modules/plugins/join-request-records.store.ts +353 -0
- package/src/modules/plugins/join-requests.service.ts +7 -4
- package/src/modules/plugins/plugin-links.service.ts +350 -0
- package/src/modules/plugins/plugin-links.ts +187 -0
- package/src/modules/plugins/plugin-provision.service.ts +356 -78
- package/src/modules/plugins/plugin-rename.service.ts +310 -0
- package/src/modules/plugins/plugins.contract.ts +79 -4
- package/src/modules/plugins/plugins.routes.ts +320 -123
- package/src/modules/plugins/plugins.service.ts +184 -57
- package/src/modules/plugins/plugins.tools.ts +88 -0
- package/src/modules/plugins/teams.routes.ts +129 -0
- package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +359 -15
- package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +248 -0
- package/src/modules/secrets-vault/__tests__/namespace-secrets.test.ts +202 -0
- package/src/modules/secrets-vault/db-secrets-vault.service.ts +217 -35
- package/src/modules/secrets-vault/index.ts +1 -0
- package/src/modules/secrets-vault/mcp-oauth-discovery.service.ts +6 -3
- package/src/modules/secrets-vault/secrets-variable-loader.ts +4 -4
- package/src/modules/secrets-vault/secrets-vault.contract.ts +93 -0
- package/src/modules/secrets-vault/secrets-vault.routes.ts +99 -38
- package/src/modules/settings/__tests__/connection-check.test.ts +233 -0
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +240 -1
- package/src/modules/settings/__tests__/git-root-folders.test.ts +247 -0
- package/src/modules/settings/__tests__/oidc-check.test.ts +185 -0
- package/src/modules/settings/__tests__/setup.routes.boot-failure.test.ts +119 -0
- package/src/modules/settings/__tests__/setup.routes.layout-phase.test.ts +170 -0
- package/src/modules/settings/__tests__/setup.routes.oidc.test.ts +555 -0
- package/src/modules/settings/__tests__/setup.routes.sync-status.test.ts +77 -0
- package/src/modules/settings/__tests__/setup.routes.test.ts +667 -16
- package/src/modules/settings/connection-check.ts +351 -0
- package/src/modules/settings/deployment-settings.service.ts +478 -39
- package/src/modules/settings/git-root-folders.ts +171 -0
- package/src/modules/settings/oidc-check.ts +229 -0
- package/src/modules/settings/setup.routes.ts +480 -110
- package/src/modules/skills/__tests__/allowed-tools-check.test.ts +224 -0
- package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +210 -0
- package/src/modules/skills/__tests__/pending-skills.service.test.ts +3 -3
- package/src/modules/skills/__tests__/skill-access-requests.routes.test.ts +128 -0
- package/src/modules/skills/__tests__/skills.routes.warnings.test.ts +91 -0
- package/src/modules/skills/__tests__/skills.service.test.ts +308 -2
- package/src/modules/skills/allowed-tools-check.ts +352 -0
- package/src/modules/skills/index.ts +5 -0
- package/src/modules/skills/pending-skills.service.ts +26 -85
- package/src/modules/skills/skill-access-requests.routes.ts +203 -0
- package/src/modules/skills/skills.contract.ts +67 -3
- package/src/modules/skills/skills.routes.ts +75 -4
- package/src/modules/skills/skills.service.ts +258 -66
- package/src/modules/skills/skills.tools.ts +43 -5
- package/src/modules/tool-auth/__tests__/connection-key-rejection.test.ts +192 -0
- package/src/modules/tool-auth/__tests__/connection-keys-admin.routes.test.ts +116 -0
- package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +124 -0
- package/src/modules/tool-auth/connection-key-rejection.ts +25 -0
- package/src/modules/tool-auth/connection-keys-admin.routes.ts +76 -0
- package/src/modules/tool-auth/external-api-key.interface.ts +73 -1
- package/src/modules/tool-auth/external-api-key.service.ts +95 -29
- package/src/modules/tool-auth/internal-token.service.ts +5 -2
- package/src/modules/tool-auth/key-or-session.middleware.ts +45 -0
- package/src/modules/tool-auth/tool-auth.middleware.ts +16 -7
- package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +228 -0
- package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +33 -4
- package/src/modules/tool-helpers/tool-context.ts +46 -7
- package/src/modules/tool-helpers/tool-handler.ts +23 -5
- package/src/modules/tool-helpers/tool.contract.ts +5 -0
- package/src/modules/tool-manuals/__tests__/mcp-json-discovery.test.ts +24 -1
- package/src/modules/tool-manuals/__tests__/mcp-server-edit.service.test.ts +2 -1
- package/src/modules/tool-manuals/__tests__/pending-tools.service.test.ts +411 -0
- package/src/modules/tool-manuals/__tests__/tool-delete.route.test.ts +133 -0
- package/src/modules/tool-manuals/__tests__/tool-delete.service.test.ts +469 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +21 -2
- package/src/modules/tool-manuals/__tests__/tool-manuals.cli.test.ts +11 -4
- package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +6 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.invalid.routes.test.ts +254 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.invalid.test.ts +577 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +6 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.pending.route.test.ts +102 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.restart.test.ts +285 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +178 -2
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +114 -2
- package/src/modules/tool-manuals/index.ts +3 -0
- package/src/modules/tool-manuals/mcp-json-discovery.ts +164 -95
- package/src/modules/tool-manuals/mcp-server-edit.service.ts +11 -14
- package/src/modules/tool-manuals/pending-tools.service.ts +179 -0
- package/src/modules/tool-manuals/tool-delete.service.ts +401 -0
- package/src/modules/tool-manuals/tool-manuals.contract.ts +165 -0
- package/src/modules/tool-manuals/tool-manuals.routes.ts +138 -54
- package/src/modules/tool-manuals/tool-manuals.service.ts +440 -104
- package/src/modules/tool-manuals/tool-manuals.tools.ts +81 -6
- package/src/modules/workflow/__tests__/apply-failure.test.ts +1038 -0
- package/src/modules/workflow/__tests__/event-bus.test.ts +61 -9
- package/src/modules/workflow/__tests__/fake-file-lock-db.ts +199 -0
- package/src/modules/workflow/__tests__/file-lock.service.path-identity.test.ts +237 -0
- package/src/modules/workflow/__tests__/format-affected-owners.test.ts +18 -0
- package/src/modules/workflow/__tests__/merge-announces-tree.test.ts +220 -0
- package/src/modules/workflow/__tests__/pending-commits.worker.concurrency.test.ts +187 -0
- package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +3 -1
- package/src/modules/workflow/__tests__/sanitize-error.test.ts +10 -0
- package/src/modules/workflow/__tests__/workflow.routes.folder-proposals.test.ts +128 -0
- package/src/modules/workflow/__tests__/workflow.routes.fork-point-file.test.ts +149 -0
- package/src/modules/workflow/__tests__/workflow.routes.lock-path.test.ts +305 -0
- package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +2 -0
- package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +3 -0
- package/src/modules/workflow/__tests__/workflow.service.deleted-branch-sweep.test.ts +2 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +296 -27
- package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +564 -0
- package/src/modules/workflow/__tests__/workflow.service.platform-restore.test.ts +135 -0
- package/src/modules/workflow/__tests__/workflow.service.read-gate.test.ts +172 -0
- package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +133 -2
- package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +442 -0
- package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +281 -0
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +158 -7
- package/src/modules/workflow/agent-tools/workflow.tools.ts +93 -64
- package/src/modules/workflow/event-bus.ts +26 -28
- package/src/modules/workflow/events.routes.ts +56 -21
- package/src/modules/workflow/file-lock.service.ts +27 -4
- package/src/modules/workflow/git/__tests__/change-request-link.test.ts +43 -0
- package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +1 -0
- package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +238 -1
- package/src/modules/workflow/git/__tests__/git.service.forkPoint.test.ts +245 -0
- package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +35 -0
- package/src/modules/workflow/git/__tests__/git.service.mergeChangeRequest.test.ts +149 -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.revertPathsAndPush.test.ts +177 -0
- package/src/modules/workflow/git/__tests__/git.service.syncFromRemote.test.ts +253 -0
- package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +114 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.getPrDetail.test.ts +69 -2
- package/src/modules/workflow/git/__tests__/pull-request.service.list-fetch.test.ts +121 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.placeholder.test.ts +79 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +1 -1
- package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-cancel.test.ts +1 -1
- package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-update.test.ts +111 -0
- package/src/modules/workflow/git/change-request-link.ts +35 -0
- package/src/modules/workflow/git/git-version.ts +5 -2
- package/src/modules/workflow/git/git.service.ts +922 -199
- package/src/modules/workflow/git/node-git-runner.ts +315 -0
- package/src/modules/workflow/git/pull-request.service.ts +188 -27
- package/src/modules/workflow/pending-commits.service.ts +26 -7
- package/src/modules/workflow/pending-commits.worker.ts +114 -69
- package/src/modules/workflow/recovery-bot.ts +4 -1
- package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +180 -1
- package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +1 -1
- package/src/modules/workflow/review-workflow/__tests__/carry-approvals-forward.test.ts +520 -0
- package/src/modules/workflow/review-workflow/__tests__/erase-approver.test.ts +84 -0
- package/src/modules/workflow/review-workflow/__tests__/merge-gate.test.ts +89 -77
- package/src/modules/workflow/review-workflow/approval-lock.ts +122 -0
- package/src/modules/workflow/review-workflow/review-workflow.interface.ts +59 -7
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +309 -105
- package/src/modules/workflow/sanitize-error.ts +9 -4
- package/src/modules/workflow/workflow.routes.ts +273 -29
- package/src/modules/workflow/workflow.service.ts +1341 -174
- package/src/modules/workspace/__tests__/empty-dirs.test.ts +44 -0
- package/src/modules/workspace/__tests__/file-stat-access.test.ts +301 -0
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +792 -0
- package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +464 -0
- package/src/modules/workspace/__tests__/workspace.routes.branch-errors.test.ts +139 -0
- package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +189 -37
- package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +51 -11
- package/src/modules/workspace/__tests__/workspace.routes.download-implies-read.test.ts +161 -0
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +109 -10
- package/src/modules/workspace/__tests__/workspace.routes.file-writes.test.ts +624 -0
- package/src/modules/workspace/__tests__/workspace.routes.folders.test.ts +409 -0
- package/src/modules/workspace/__tests__/workspace.routes.move-platform-files.test.ts +327 -0
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +31 -13
- package/src/modules/workspace/__tests__/workspace.service.ensure-remotes-fetched.test.ts +109 -0
- package/src/modules/workspace/__tests__/workspace.service.list-cloned.test.ts +94 -0
- package/src/modules/workspace/__tests__/workspace.service.missing-branch.test.ts +28 -0
- package/src/modules/workspace/__tests__/workspace.service.move-never-overwrites.test.ts +218 -0
- package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +98 -13
- package/src/modules/workspace/__tests__/workspace.service.test.ts +811 -42
- package/src/modules/workspace/__tests__/workspace.service.unknown-branch.test.ts +197 -0
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +128 -0
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +212 -0
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +2632 -110
- package/src/modules/workspace/__tests__/write-denial.test.ts +75 -0
- package/src/modules/workspace/empty-dirs.ts +79 -0
- package/src/modules/workspace/file-readers/README.md +32 -0
- package/src/modules/workspace/file-readers/__tests__/doc-extract.test.ts +59 -2
- package/src/modules/workspace/file-readers/__tests__/file-reader.registry.test.ts +133 -4
- package/src/modules/workspace/file-readers/__tests__/fixtures/soft-break.docx +0 -0
- package/src/modules/workspace/file-readers/__tests__/fixtures/tab.docx +0 -0
- package/src/modules/workspace/file-readers/content-mode.ts +87 -0
- package/src/modules/workspace/file-readers/doc-extract.service.ts +4 -1
- package/src/modules/workspace/file-readers/doc-extract.types.ts +8 -4
- package/src/modules/workspace/file-readers/document-reader.ts +6 -0
- package/src/modules/workspace/file-readers/extract-docx.ts +6 -0
- package/src/modules/workspace/file-readers/file-reader.registry.ts +9 -1
- package/src/modules/workspace/file-readers/file-reader.ts +27 -6
- package/src/modules/workspace/file-readers/image-reader.ts +10 -5
- package/src/modules/workspace/file-readers/ooxml-text.ts +38 -2
- package/src/modules/workspace/file-readers/text-reader.ts +61 -4
- package/src/modules/workspace/git-internals.middleware.ts +86 -0
- package/src/modules/workspace/not-found.ts +119 -0
- package/src/modules/workspace/session-ontology.gate.ts +1 -1
- package/src/modules/workspace/startup/__tests__/beside-checkout.test.ts +181 -0
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.degraded.test.ts +361 -0
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +53 -16
- package/src/modules/workspace/startup/beside-checkout.ts +112 -0
- package/src/modules/workspace/startup/kb-git.ts +43 -36
- package/src/modules/workspace/startup/kb-startup-runner.ts +303 -63
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +1405 -48
- package/src/modules/workspace/startup/steps/groups-to-plugins.step.ts +460 -417
- package/src/modules/workspace/startup/steps/personal-spaces.step.ts +191 -0
- package/src/modules/workspace/startup/steps/plugin-display-names.step.ts +169 -0
- package/src/modules/workspace/startup/steps/plugin-layout.ts +110 -0
- package/src/modules/workspace/startup/steps/plugin-manifests.step.ts +90 -0
- package/src/modules/workspace/startup/steps/roles-yaml.step.ts +6 -12
- package/src/modules/workspace/startup/steps/seed-tree.ts +177 -54
- package/src/modules/workspace/startup/steps/template-files.step.ts +581 -126
- package/src/modules/workspace/startup/steps/template-source.ts +168 -0
- package/src/modules/workspace/workspace.routes.ts +691 -269
- package/src/modules/workspace/workspace.service.ts +1071 -292
- package/src/modules/workspace/workspace.tools.ts +1787 -180
- package/src/modules/workspace/write-denial.ts +189 -0
- package/src/shared/__tests__/canonical-file-identity.test.ts +154 -0
- package/src/shared/__tests__/email-identity.test.ts +35 -0
- package/src/shared/__tests__/filename.test.ts +0 -0
- package/src/shared/__tests__/frontmatter-id.test.ts +5 -0
- package/src/shared/__tests__/git-failure.test.ts +198 -0
- package/src/shared/__tests__/git-internals.test.ts +115 -0
- package/src/shared/__tests__/http-errors.test.ts +42 -0
- package/src/shared/__tests__/logging.test.ts +185 -0
- package/src/shared/__tests__/path-containment.test.ts +60 -0
- package/src/shared/__tests__/path-order.test.ts +24 -0
- package/src/shared/__tests__/pending-proposals.readAt.test.ts +102 -0
- package/src/shared/__tests__/printable.test.ts +64 -0
- package/src/shared/__tests__/redact-secret.test.ts +122 -0
- package/src/shared/__tests__/rename-no-replace.test.ts +466 -0
- package/src/shared/__tests__/ssrf.test.ts +20 -0
- package/src/shared/__tests__/ttl-cache.test.ts +55 -0
- package/src/shared/canonical-file-identity.ts +92 -0
- package/src/shared/domain-errors.ts +178 -0
- package/src/shared/email-identity.ts +38 -0
- package/src/shared/fs.contract.ts +225 -0
- package/src/shared/git-failure.ts +196 -0
- package/src/shared/git-internals.ts +124 -0
- package/src/shared/git.contract.ts +170 -0
- package/src/shared/http-errors.ts +36 -0
- package/src/shared/logger.contract.ts +141 -0
- package/src/shared/logging.ts +96 -0
- package/src/shared/path-containment.ts +34 -0
- package/src/shared/path-order.ts +33 -0
- package/src/shared/pending-proposals.ts +161 -0
- package/src/shared/printable.ts +68 -0
- package/src/shared/redact-secret.ts +118 -0
- package/src/shared/rename-no-replace.ts +386 -0
- package/src/shared/ssrf.ts +44 -16
- package/src/shared/ttl-cache.ts +33 -1
- package/src/version.ts +10 -6
- package/src/modules/diff/diff-paths.ts +0 -9
- package/src/modules/mcp/__tests__/mcp-session-store.test.ts +0 -151
- package/src/modules/mcp/__tests__/mcp.routes.session.test.ts +0 -226
- package/src/modules/mcp/mcp-session-store.ts +0 -162
- package/src/shared/__tests__/fs-walk.test.ts +0 -42
- package/src/shared/fs-walk.ts +0 -30
- package/src/shared/hash-email.ts +0 -14
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import {
|
|
3
|
+
import { logger } from '../../shared/logging.js';
|
|
4
|
+
const log = logger('access');
|
|
5
|
+
import { isAbsence } from '../../shared/fs.contract.js';
|
|
6
|
+
import { isGitTimeout } from '../../shared/git.contract.js';
|
|
7
|
+
import { hashEmail } from '../../shared/email-identity.js';
|
|
8
|
+
import { NodeGitRunner } from '../workflow/git/node-git-runner.js';
|
|
9
|
+
import { PLUGINS_DIR, PLUGIN_MANIFEST_FILE, isPersonalPluginDir, pluginIdentityOf, platformRestoreDestination, } from '@bevel-software/platform-shared';
|
|
6
10
|
import { AccessConfigError, AccessUnreadableError } from '../access-model/access-errors.js';
|
|
11
|
+
import { WorkflowDomainError } from '../../shared/domain-errors.js';
|
|
12
|
+
import { synthesizePluginPrincipals } from '../access-model/plugin-principals.js';
|
|
7
13
|
import { GROUPS_YAML, SYNCED_GROUPS_YAML, parseGroupsFile, } from '../access-model/group-files.js';
|
|
8
|
-
import { ADMIN_CANONICAL, EVERYONE_CANONICAL, EVERYONE_DISPLAY, KNOWN_VERBS, ROLE_TOKEN_PREFIX, accessMdSelfEntries, canonicalEmail, canonicalRoleName, isAccessMdPath, mergeGroupsIntoRoles, parseAccessFile, parseOwnAccessEntries, parseRolesYaml, sourceVerbsFor, } from '../access-model/access-grammar.js';
|
|
14
|
+
import { ADMIN_CANONICAL, EVERYONE_CANONICAL, EVERYONE_DISPLAY, KNOWN_VERBS, ROLE_TOKEN_PREFIX, accessMdSelfEntries, canonicalEmail, canonicalRoleName, isAccessMdPath, mergeGroupsIntoRoles, parseAccessFile, parseOwnAccessEntries, parseRolesYaml, requiredVerbsFor, sourceVerbsFor, } from '../access-model/access-grammar.js';
|
|
9
15
|
import { DIRECTORY_SYNC_BOT_EMAIL, DIRECTORY_SYNC_BOT_NAME, } from './directory-sync-bot.js';
|
|
10
|
-
const execFileAsync = promisify(execFile);
|
|
11
16
|
/**
|
|
12
17
|
* Read many objects from a git repo in ONE `git cat-file --batch` process.
|
|
13
18
|
* `specs` are `<ref>:<path>` lines; the result array is index-aligned with
|
|
@@ -16,61 +21,36 @@ const execFileAsync = promisify(execFile);
|
|
|
16
21
|
* request: `<oid> <type> <size>\n<size bytes>\n`, or `<spec> missing\n`
|
|
17
22
|
* (the spec may itself contain spaces, hence the endsWith checks).
|
|
18
23
|
*/
|
|
19
|
-
function catFileBatch(repoDir, specs) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const errChunks = [];
|
|
26
|
-
child.stdout.on('data', (c) => chunks.push(c));
|
|
27
|
-
child.stderr.on('data', (c) => errChunks.push(c));
|
|
28
|
-
// A dying git can close stdin mid-write; the 'close' handler below still
|
|
29
|
-
// fires with the exit code, which is the error we want to surface.
|
|
30
|
-
child.stdin.on('error', () => undefined);
|
|
31
|
-
child.on('error', reject);
|
|
32
|
-
child.on('close', (code) => {
|
|
33
|
-
if (code !== 0) {
|
|
34
|
-
reject(new Error(`git cat-file --batch exited ${code}: ${Buffer.concat(errChunks).toString('utf-8').trim()}`));
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
try {
|
|
38
|
-
const out = Buffer.concat(chunks);
|
|
39
|
-
const results = [];
|
|
40
|
-
let off = 0;
|
|
41
|
-
for (let i = 0; i < specs.length; i++) {
|
|
42
|
-
const nl = out.indexOf(0x0a, off);
|
|
43
|
-
if (nl < 0)
|
|
44
|
-
throw new Error('unexpected end of git cat-file output');
|
|
45
|
-
const header = out.subarray(off, nl).toString('utf-8');
|
|
46
|
-
off = nl + 1;
|
|
47
|
-
if (header.endsWith(' missing') || header.endsWith(' ambiguous')) {
|
|
48
|
-
results.push(null);
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
const parts = header.split(' ');
|
|
52
|
-
const size = Number(parts[2]);
|
|
53
|
-
if (parts.length !== 3 || !Number.isInteger(size) || size < 0) {
|
|
54
|
-
throw new Error(`unexpected git cat-file header: ${header}`);
|
|
55
|
-
}
|
|
56
|
-
// Non-blob (a directory path resolves to a tree) → null, but the
|
|
57
|
-
// payload still has to be skipped to stay aligned.
|
|
58
|
-
results.push(parts[1] === 'blob' ? out.subarray(off, off + size).toString('utf-8') : null);
|
|
59
|
-
off += size + 1; // payload + trailing LF
|
|
60
|
-
}
|
|
61
|
-
resolve(results);
|
|
62
|
-
}
|
|
63
|
-
catch (err) {
|
|
64
|
-
reject(err);
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
child.stdin.write(`${specs.join('\n')}\n`);
|
|
68
|
-
child.stdin.end();
|
|
24
|
+
async function catFileBatch(runner, repoDir, specs) {
|
|
25
|
+
// Bytes, not text: every `<size>` in the header counts bytes, and the walk
|
|
26
|
+
// below has to step by that count through content that is not all ASCII.
|
|
27
|
+
const { stdout: out } = await runner.run(repoDir, ['cat-file', '--batch'], {
|
|
28
|
+
input: `${specs.join('\n')}\n`,
|
|
29
|
+
encoding: 'buffer',
|
|
69
30
|
});
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
31
|
+
const results = [];
|
|
32
|
+
let off = 0;
|
|
33
|
+
for (let i = 0; i < specs.length; i++) {
|
|
34
|
+
const nl = out.indexOf(0x0a, off);
|
|
35
|
+
if (nl < 0)
|
|
36
|
+
throw new Error('unexpected end of git cat-file output');
|
|
37
|
+
const header = out.subarray(off, nl).toString('utf-8');
|
|
38
|
+
off = nl + 1;
|
|
39
|
+
if (header.endsWith(' missing') || header.endsWith(' ambiguous')) {
|
|
40
|
+
results.push(null);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const parts = header.split(' ');
|
|
44
|
+
const size = Number(parts[2]);
|
|
45
|
+
if (parts.length !== 3 || !Number.isInteger(size) || size < 0) {
|
|
46
|
+
throw new Error(`unexpected git cat-file header: ${header}`);
|
|
47
|
+
}
|
|
48
|
+
// Non-blob (a directory path resolves to a tree) → null, but the
|
|
49
|
+
// payload still has to be skipped to stay aligned.
|
|
50
|
+
results.push(parts[1] === 'blob' ? out.subarray(off, off + size).toString('utf-8') : null);
|
|
51
|
+
off += size + 1; // payload + trailing LF
|
|
52
|
+
}
|
|
53
|
+
return results;
|
|
74
54
|
}
|
|
75
55
|
function isBuiltInRole(canonicalRole) {
|
|
76
56
|
return canonicalRole === EVERYONE_CANONICAL;
|
|
@@ -78,11 +58,6 @@ function isBuiltInRole(canonicalRole) {
|
|
|
78
58
|
function roleKnown(roles, canonicalRole) {
|
|
79
59
|
return roles.byCanonical.has(canonicalRole) || isBuiltInRole(canonicalRole);
|
|
80
60
|
}
|
|
81
|
-
/** True for the errno codes that mean "the file genuinely is not there". */
|
|
82
|
-
function isAbsenceError(err) {
|
|
83
|
-
const code = err?.code;
|
|
84
|
-
return code === 'ENOENT' || code === 'ENOTDIR';
|
|
85
|
-
}
|
|
86
61
|
/**
|
|
87
62
|
* Load the ACTIVE group source through `read` (working tree or at-ref — the
|
|
88
63
|
* caller supplies the reader, so both model loaders share one mode rule):
|
|
@@ -90,13 +65,13 @@ function isAbsenceError(err) {
|
|
|
90
65
|
* even when the synced file is empty or malformed — falling back would
|
|
91
66
|
* resurrect retired manual groups); otherwise `groups.yaml` → manual mode.
|
|
92
67
|
*
|
|
93
|
-
* `read` returns null for a genuinely-absent file
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
68
|
+
* `read` returns null for a genuinely-absent file — ENOENT/ENOTDIR, the
|
|
69
|
+
* reader's judgement through the fs probe — and THROWS for any other failure.
|
|
70
|
+
* Every thrown error — notably one on `synced-groups.yaml` — is treated as a
|
|
71
|
+
* BROKEN source: no groups, no fallback to the manual file (falling back
|
|
72
|
+
* would resurrect retired groups), and an `ok: false` health marker.
|
|
73
|
+
* Structural parse failures degrade the same way; this function never throws
|
|
74
|
+
* (except to let an `AccessUnreadableError` through — see below).
|
|
100
75
|
*/
|
|
101
76
|
export async function loadActiveGroups(read) {
|
|
102
77
|
const broken = (file, reason) => ({
|
|
@@ -119,15 +94,10 @@ export async function loadActiveGroups(read) {
|
|
|
119
94
|
// the same error; groups cannot be the one input that does not.
|
|
120
95
|
if (err instanceof AccessUnreadableError)
|
|
121
96
|
throw err;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// A non-absence read error on the SYNCED source must NOT fall back to
|
|
127
|
-
// groups.yaml — the synced file may exist and its manual predecessor is
|
|
128
|
-
// retired. Broken-groups instead.
|
|
129
|
-
return broken(SYNCED_GROUPS_YAML, err instanceof Error ? err.message : String(err));
|
|
130
|
-
}
|
|
97
|
+
// A read error on the SYNCED source must NOT fall back to groups.yaml —
|
|
98
|
+
// the synced file may exist and its manual predecessor is retired.
|
|
99
|
+
// Broken-groups instead.
|
|
100
|
+
return broken(SYNCED_GROUPS_YAML, err instanceof Error ? err.message : String(err));
|
|
131
101
|
}
|
|
132
102
|
const sourceFile = syncedText !== null ? SYNCED_GROUPS_YAML : GROUPS_YAML;
|
|
133
103
|
let text;
|
|
@@ -141,10 +111,7 @@ export async function loadActiveGroups(read) {
|
|
|
141
111
|
catch (err) {
|
|
142
112
|
if (err instanceof AccessUnreadableError)
|
|
143
113
|
throw err; // see above
|
|
144
|
-
|
|
145
|
-
text = null;
|
|
146
|
-
else
|
|
147
|
-
return broken(GROUPS_YAML, err instanceof Error ? err.message : String(err));
|
|
114
|
+
return broken(GROUPS_YAML, err instanceof Error ? err.message : String(err));
|
|
148
115
|
}
|
|
149
116
|
}
|
|
150
117
|
if (text === null)
|
|
@@ -165,17 +132,84 @@ export async function loadActiveGroups(read) {
|
|
|
165
132
|
* `hasPermissionResolved`). `collapseScopes` flattens this into the
|
|
166
133
|
* closest-wins-per-principal view the display helpers use.
|
|
167
134
|
*/
|
|
135
|
+
/**
|
|
136
|
+
* The rules listener on the one walk of the checkout: every `access.md` at
|
|
137
|
+
* any depth (the access tree is structure-agnostic — the root's included),
|
|
138
|
+
* plus every plugin manifest under the plugins root, for principal
|
|
139
|
+
* synthesis. A reader: a folder the walk could not list is simply not in
|
|
140
|
+
* the model (a writer that needs completeness asks the walk for its holes).
|
|
141
|
+
*
|
|
142
|
+
* Per-file failures (malformed YAML, bad role refs, unknown verbs) are
|
|
143
|
+
* logged and the offending file is dropped from the model — they do NOT
|
|
144
|
+
* throw. A typo in one nested access.md must not 500 the entire editor;
|
|
145
|
+
* admins can still write `access.md` / `roles.yaml` because
|
|
146
|
+
* `hasPermissionResolved` admin-rescues those paths, so the bad config
|
|
147
|
+
* remains fixable from inside the app.
|
|
148
|
+
*/
|
|
149
|
+
function accessFileListener(repoDir, out, pluginDirs) {
|
|
150
|
+
return {
|
|
151
|
+
async onFile(dir, name) {
|
|
152
|
+
const rel = dir ? `${dir}/${name}` : name;
|
|
153
|
+
if (name === PLUGIN_MANIFEST_FILE && dir.startsWith(`${PLUGINS_DIR}/`)) {
|
|
154
|
+
const text = await fs.readFile(path.join(repoDir, rel), 'utf-8').catch(() => null);
|
|
155
|
+
pluginDirs.set(dir, pluginIdentityOf(parseManifestText(text), path.posix.basename(dir)));
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (name !== 'access.md')
|
|
159
|
+
return;
|
|
160
|
+
const text = await fs.readFile(path.join(repoDir, rel), 'utf-8');
|
|
161
|
+
const parsed = parseAccessFile(text, rel);
|
|
162
|
+
if (!parsed.ok) {
|
|
163
|
+
// Treat as if the file didn't exist for resolution purposes.
|
|
164
|
+
// Admin-rescue on access.md paths still lets an admin fix it.
|
|
165
|
+
for (const e of parsed.errors)
|
|
166
|
+
log.warn(`${e} — file ignored`);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
for (const w of parsed.warnings)
|
|
170
|
+
log.warn(w);
|
|
171
|
+
if (out.has(parsed.file.dir)) {
|
|
172
|
+
log.warn(`${rel}: duplicate access.md for directory '${parsed.file.dir}' — keeping the first one seen`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
out.set(parsed.file.dir, parsed.file);
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/** A manifest's parsed object, or null for absent, unparsable or not-an-object text. */
|
|
180
|
+
function parseManifestText(text) {
|
|
181
|
+
if (text === null)
|
|
182
|
+
return null;
|
|
183
|
+
try {
|
|
184
|
+
const parsed = JSON.parse(text);
|
|
185
|
+
return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)
|
|
186
|
+
? parsed
|
|
187
|
+
: null;
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
168
193
|
function resolveScopes(model, verb, relativePath, fileOwn) {
|
|
169
194
|
const target = verb;
|
|
195
|
+
// Both lists come from the grammar's one dependency graph (`VERB_REQUIRES`):
|
|
196
|
+
// the verbs whose grant confers the target, and the verbs the target
|
|
197
|
+
// presupposes, whose denial strips it.
|
|
170
198
|
const supersets = sourceVerbsFor(verb).filter((v) => v !== verb);
|
|
199
|
+
const prerequisites = requiredVerbsFor(verb).filter((v) => v !== verb);
|
|
171
200
|
// Build one scope's effective verdicts. The target verb contributes both
|
|
172
|
-
// grants and denials
|
|
173
|
-
// never strips `read`)
|
|
174
|
-
//
|
|
175
|
-
//
|
|
201
|
+
// grants and denials. Superset verbs contribute grants only (a `deny write`
|
|
202
|
+
// never strips `read`); prerequisite verbs contribute DENIALS only (a `deny
|
|
203
|
+
// write` strips `owner`, a `deny read` strips all three — nobody owns what
|
|
204
|
+
// they may not edit, or edits what they may not open), and a `read` grant
|
|
205
|
+
// confers no `write`. Within the scope a grant always wins over a deny of
|
|
206
|
+
// the same principal, so a same-file grant of the target or of a superset
|
|
207
|
+
// overrides a deny of the target or of a prerequisite (`owner:` beats `deny
|
|
208
|
+
// write`, `write:` beats `deny read`).
|
|
176
209
|
const buildScope = (entries, filterRoles, source) => {
|
|
177
210
|
const byRole = new Map();
|
|
178
211
|
const byEmail = new Map();
|
|
212
|
+
const implied = { byRole: new Set(), byEmail: new Set() };
|
|
179
213
|
const set = (entry, state) => {
|
|
180
214
|
const map = entry.kind === 'role' ? byRole : byEmail;
|
|
181
215
|
const key = entry.kind === 'role' ? entry.role : entry.email;
|
|
@@ -183,8 +217,9 @@ function resolveScopes(model, verb, relativePath, fileOwn) {
|
|
|
183
217
|
return; // a grant in this scope sticks
|
|
184
218
|
map.set(key, state);
|
|
185
219
|
};
|
|
220
|
+
const skip = (entry) => filterRoles && entry.kind === 'role' && !roleKnown(model.roles, entry.role);
|
|
186
221
|
for (const entry of entries[target]) {
|
|
187
|
-
if (
|
|
222
|
+
if (skip(entry))
|
|
188
223
|
continue;
|
|
189
224
|
set(entry, entry.deny ? 'denied' : 'grant');
|
|
190
225
|
}
|
|
@@ -192,12 +227,53 @@ function resolveScopes(model, verb, relativePath, fileOwn) {
|
|
|
192
227
|
for (const entry of entries[src]) {
|
|
193
228
|
if (entry.deny)
|
|
194
229
|
continue; // grant-only fold
|
|
195
|
-
if (
|
|
230
|
+
if (skip(entry))
|
|
196
231
|
continue;
|
|
197
232
|
set(entry, 'grant');
|
|
198
233
|
}
|
|
199
234
|
}
|
|
200
|
-
|
|
235
|
+
// Last, so a target or superset GRANT already in the map stands, and a
|
|
236
|
+
// target's own `deny` line is never re-labelled as implied.
|
|
237
|
+
for (const src of prerequisites) {
|
|
238
|
+
for (const entry of entries[src]) {
|
|
239
|
+
if (!entry.deny)
|
|
240
|
+
continue; // deny-only fold
|
|
241
|
+
if (skip(entry))
|
|
242
|
+
continue;
|
|
243
|
+
const map = entry.kind === 'role' ? byRole : byEmail;
|
|
244
|
+
const key = entry.kind === 'role' ? entry.role : entry.email;
|
|
245
|
+
if (map.has(key))
|
|
246
|
+
continue; // an own verdict (grant or deny) is already the answer
|
|
247
|
+
map.set(key, 'denied');
|
|
248
|
+
(entry.kind === 'role' ? implied.byRole : implied.byEmail).add(key);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// The anonymous caller's verdict at this scope — what someone who holds
|
|
252
|
+
// exactly the PUBLIC keys (a plugin principal whose plugin admits
|
|
253
|
+
// everyone, so everyone holds it) resolves to, by the same tiers a named
|
|
254
|
+
// caller gets: a grant via any public key wins, else a denial via one
|
|
255
|
+
// denies, else the built-in `everyone` entry decides. Derived HERE, where
|
|
256
|
+
// a scope's verdicts are built, so every reader of it — the permission
|
|
257
|
+
// check, the eligible lists, the `restricted` flag, the compiler's
|
|
258
|
+
// everyone audience — sees one truth; and derived into its own field,
|
|
259
|
+
// never into `byRole`, so the entries a person can remove stay exactly
|
|
260
|
+
// the lines the files hold.
|
|
261
|
+
let publicGrant = false;
|
|
262
|
+
let publicDeny = false;
|
|
263
|
+
for (const [key, state] of byRole) {
|
|
264
|
+
if (!model.roles.publicKeys?.has(key))
|
|
265
|
+
continue;
|
|
266
|
+
if (state === 'grant')
|
|
267
|
+
publicGrant = true;
|
|
268
|
+
else if (state === 'denied')
|
|
269
|
+
publicDeny = true;
|
|
270
|
+
}
|
|
271
|
+
const everyone = publicGrant
|
|
272
|
+
? 'grant'
|
|
273
|
+
: publicDeny
|
|
274
|
+
? 'denied'
|
|
275
|
+
: byRole.get(EVERYONE_CANONICAL);
|
|
276
|
+
return { byRole, byEmail, everyone, source, implied };
|
|
201
277
|
};
|
|
202
278
|
const scopes = [];
|
|
203
279
|
// The node's own frontmatter is the most specific scope. Role refs there are
|
|
@@ -221,17 +297,124 @@ function resolveScopes(model, verb, relativePath, fileOwn) {
|
|
|
221
297
|
function collapseScopes(scopes) {
|
|
222
298
|
const byRole = new Map();
|
|
223
299
|
const byEmail = new Map();
|
|
300
|
+
let everyone;
|
|
224
301
|
for (let i = scopes.length - 1; i >= 0; i--) {
|
|
225
302
|
for (const [k, v] of scopes[i].byRole)
|
|
226
303
|
byRole.set(k, v);
|
|
227
304
|
for (const [k, v] of scopes[i].byEmail)
|
|
228
305
|
byEmail.set(k, v);
|
|
306
|
+
if (scopes[i].everyone !== undefined)
|
|
307
|
+
everyone = scopes[i].everyone;
|
|
229
308
|
}
|
|
230
|
-
return { byRole, byEmail };
|
|
309
|
+
return { byRole, byEmail, everyone };
|
|
231
310
|
}
|
|
232
311
|
function resolveAtPath(model, verb, relativePath, fileOwn) {
|
|
233
312
|
return collapseScopes(resolveScopes(model, verb, relativePath, fileOwn));
|
|
234
313
|
}
|
|
314
|
+
/**
|
|
315
|
+
* Every principal key a caller holds: the tokens their email is a member of,
|
|
316
|
+
* plus the PUBLIC keys every caller holds regardless (a plugin principal
|
|
317
|
+
* whose plugin grants `everyone`). Undefined when there is nothing at all,
|
|
318
|
+
* matching the plain `byEmail.get` the tier-2 check used to read.
|
|
319
|
+
*/
|
|
320
|
+
function principalKeysOf(model, email) {
|
|
321
|
+
const own = model.roles.byEmail.get(email);
|
|
322
|
+
const pub = model.roles.publicKeys;
|
|
323
|
+
if (!pub?.size)
|
|
324
|
+
return own;
|
|
325
|
+
return new Set([...(own ?? []), ...pub]);
|
|
326
|
+
}
|
|
327
|
+
/** The explicit key of the Admin ROLE — never the bare token, which a same-named group may own. */
|
|
328
|
+
const ADMIN_ROLE_KEY = `${ROLE_TOKEN_PREFIX}${ADMIN_CANONICAL}`;
|
|
329
|
+
/**
|
|
330
|
+
* Add to `keys` what holding them confers on top: every plugin principal
|
|
331
|
+
* expanded from one of the keys, and the public keys every caller holds.
|
|
332
|
+
*/
|
|
333
|
+
function addDerivedKeys(model, keys) {
|
|
334
|
+
for (const [key, principal] of model.roles.byCanonical) {
|
|
335
|
+
if (principal.kind !== 'plugin' || !principal.sourceKeys)
|
|
336
|
+
continue;
|
|
337
|
+
for (const source of principal.sourceKeys) {
|
|
338
|
+
if (keys.has(source)) {
|
|
339
|
+
keys.add(key);
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
for (const key of model.roles.publicKeys ?? [])
|
|
345
|
+
keys.add(key);
|
|
346
|
+
return keys;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Every principal key that being in `group` confers — the group's OWN key,
|
|
350
|
+
* the roles that list the group (`group:<Name>` in roles.yaml), the plugin
|
|
351
|
+
* principals whose roster admits the group directly or through one of those
|
|
352
|
+
* roles, and the public keys every caller holds. This is the group-shaped
|
|
353
|
+
* twin of `principalKeysOf`: a member's key set minus everything they hold
|
|
354
|
+
* for reasons of their own. `null` when no group of that name exists.
|
|
355
|
+
*
|
|
356
|
+
* Groups are keyed by their bare canonical name (`mergeGroupsIntoRoles`);
|
|
357
|
+
* roles by `role/<name>`. A bare key whose record is a ROLE is not a group,
|
|
358
|
+
* however it was spelled.
|
|
359
|
+
*/
|
|
360
|
+
function principalKeysOfGroup(model, group) {
|
|
361
|
+
const canonical = canonicalRoleName(group);
|
|
362
|
+
const record = model.roles.byCanonical.get(canonical);
|
|
363
|
+
if (!record || record.kind !== 'group')
|
|
364
|
+
return null;
|
|
365
|
+
const keys = new Set([canonical]);
|
|
366
|
+
for (const [key, principal] of model.roles.byCanonical) {
|
|
367
|
+
if (principal.kind === 'role' && principal.groupRefs?.has(canonical))
|
|
368
|
+
keys.add(key);
|
|
369
|
+
}
|
|
370
|
+
// Plugin principals were expanded from role/group entries; a plugin that
|
|
371
|
+
// admits the group, or a role the group is folded into, admits the group.
|
|
372
|
+
return addDerivedKeys(model, keys);
|
|
373
|
+
}
|
|
374
|
+
/** The key set of a caller who holds nothing: `hasPermissionForKeys` then answers as `everyone`. */
|
|
375
|
+
const NO_KEYS = new Set();
|
|
376
|
+
/**
|
|
377
|
+
* The tier-2 half of `hasPermissionResolved` on its own: a verdict for a set
|
|
378
|
+
* of principal KEYS with no person behind them — no direct-email tier, no
|
|
379
|
+
* admin rescue, no machine-owned rule — only the Admin write floor, when the
|
|
380
|
+
* keys include the Admin role. Closest scope first; within a scope a
|
|
381
|
+
* grant to any key wins over a denial to another, exactly as for a person.
|
|
382
|
+
*/
|
|
383
|
+
function hasPermissionForKeys(model, verb, keys, relativePath, fileOwn) {
|
|
384
|
+
const adminFloor = verb === 'write' && keys.has(ADMIN_ROLE_KEY);
|
|
385
|
+
for (const scope of resolveScopes(model, verb, relativePath, fileOwn)) {
|
|
386
|
+
if (adminFloor && isAdminFloorScope(scope, relativePath))
|
|
387
|
+
return true;
|
|
388
|
+
let grant = false;
|
|
389
|
+
let deny = false;
|
|
390
|
+
for (const key of keys) {
|
|
391
|
+
const state = scope.byRole.get(key);
|
|
392
|
+
if (state === 'denied')
|
|
393
|
+
deny = true;
|
|
394
|
+
else if (state === 'grant')
|
|
395
|
+
grant = true;
|
|
396
|
+
}
|
|
397
|
+
if (grant)
|
|
398
|
+
return true;
|
|
399
|
+
if (deny)
|
|
400
|
+
return false;
|
|
401
|
+
if (scope.everyone)
|
|
402
|
+
return scope.everyone === 'grant';
|
|
403
|
+
}
|
|
404
|
+
return adminFloor;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Whether a scope sits at the Admin write floor: the repository root's own
|
|
408
|
+
* `access.md`, or the own frontmatter of a file directly in the root (those
|
|
409
|
+
* files are part of the root, so their own rules cannot take Admin's write
|
|
410
|
+
* away either). A folder directly in the root is not covered — its own
|
|
411
|
+
* `access.md` is an ordinary subfolder scope that may exclude Admin.
|
|
412
|
+
*/
|
|
413
|
+
function isAdminFloorScope(scope, relativePath) {
|
|
414
|
+
if (scope.source.kind === 'own')
|
|
415
|
+
return !relativePath.includes('/');
|
|
416
|
+
return scope.source.path === 'access.md';
|
|
417
|
+
}
|
|
235
418
|
function isAdminEmail(model, email) {
|
|
236
419
|
if (model.deploymentOwners.has(email))
|
|
237
420
|
return true;
|
|
@@ -239,7 +422,7 @@ function isAdminEmail(model, email) {
|
|
|
239
422
|
// Check the explicit `role/admin` alias, NOT the bare token: bare-name
|
|
240
423
|
// precedence is group-first, so a group that happens to be named "Admin"
|
|
241
424
|
// owns the bare key — and its members must never inherit the capability.
|
|
242
|
-
return !!roles && roles.has(
|
|
425
|
+
return !!roles && roles.has(ADMIN_ROLE_KEY);
|
|
243
426
|
}
|
|
244
427
|
/**
|
|
245
428
|
* Resolve whether `userEmail` has `verb` on `relativePath`.
|
|
@@ -272,48 +455,76 @@ function isAdminEmail(model, email) {
|
|
|
272
455
|
* owner is whoever can already set `ADMIN_EMAIL`, so this concedes no
|
|
273
456
|
* authority they did not have; it only gives it a door.
|
|
274
457
|
*
|
|
458
|
+
* One floor for `write`, applied during the walk: an admin (same definition)
|
|
459
|
+
* always holds `write` at the repository root. When the walk reaches the root
|
|
460
|
+
* `access.md` (or the own frontmatter of a file directly in the root) — or runs
|
|
461
|
+
* out of scopes without a verdict — an admin is granted,
|
|
462
|
+
* whatever the root file says, so no root rule can lock the deployment out of
|
|
463
|
+
* its own tree. A NEARER scope still decides first: a subfolder that denies
|
|
464
|
+
* Admin (or denies `everyone` without naming Admin) excludes admins there like
|
|
465
|
+
* anyone else. The root file's own entries are not rewritten — the floor is a
|
|
466
|
+
* verdict, not a grant line — so `grantSources` stays file-backed.
|
|
467
|
+
*
|
|
275
468
|
* No special-cases for `read`/`download` — they fall through to scope resolution.
|
|
276
469
|
*/
|
|
277
470
|
function hasPermissionResolved(model, verb, userEmail, relativePath, fileOwn) {
|
|
471
|
+
return decidePermissionResolved(model, verb, userEmail, relativePath, fileOwn).allowed;
|
|
472
|
+
}
|
|
473
|
+
function decidePermissionResolved(model, verb, userEmail, relativePath, fileOwn) {
|
|
278
474
|
const email = canonicalEmail(userEmail);
|
|
279
475
|
if (verb === 'write') {
|
|
280
|
-
if (relativePath === 'roles.yaml')
|
|
281
|
-
return isAdminEmail(model, email);
|
|
282
|
-
|
|
283
|
-
|
|
476
|
+
if (relativePath === 'roles.yaml') {
|
|
477
|
+
return { allowed: isAdminEmail(model, email), scope: null, via: 'admin-rescue' };
|
|
478
|
+
}
|
|
479
|
+
if (isAccessMdPath(relativePath) && isAdminEmail(model, email)) {
|
|
480
|
+
return { allowed: true, scope: null, via: 'admin-rescue' };
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
const adminFloor = verb === 'write' && isAdminEmail(model, email);
|
|
484
|
+
// For write, the deployment owner IS Admin — the floor admits them, so the
|
|
485
|
+
// Admin rules along the way bind them too: a subfolder denying Admin excludes
|
|
486
|
+
// the owner exactly as it excludes the role's members.
|
|
487
|
+
let userRoles = principalKeysOf(model, email);
|
|
488
|
+
if (adminFloor && !userRoles?.has(ADMIN_ROLE_KEY)) {
|
|
489
|
+
userRoles = new Set([...(userRoles ?? []), ...principalKeysOfToken(model, ADMIN_ROLE_KEY)]);
|
|
284
490
|
}
|
|
285
|
-
const userRoles = model.roles.byEmail.get(email);
|
|
286
491
|
const scopes = resolveScopes(model, verb, relativePath, fileOwn);
|
|
287
492
|
for (const scope of scopes) {
|
|
493
|
+
if (adminFloor && isAdminFloorScope(scope, relativePath))
|
|
494
|
+
return { allowed: true, scope, via: 'admin-floor' };
|
|
288
495
|
// Tier 1 — direct email entry is the most specific verdict at this scope.
|
|
289
496
|
const direct = scope.byEmail.get(email);
|
|
290
497
|
if (direct)
|
|
291
|
-
return direct === 'grant';
|
|
498
|
+
return { allowed: direct === 'grant', scope, via: 'email', key: email };
|
|
292
499
|
// Tier 2 — the caller's roles. A grant via any one of them wins over a deny
|
|
293
500
|
// via another at this same scope (a `deny Engineer` doesn't undo an
|
|
294
501
|
// unrelated `Admin` grant).
|
|
295
502
|
if (userRoles && userRoles.size) {
|
|
296
|
-
let
|
|
297
|
-
let
|
|
503
|
+
let granted;
|
|
504
|
+
let denied;
|
|
298
505
|
for (const r of userRoles) {
|
|
299
506
|
const s = scope.byRole.get(r);
|
|
300
507
|
if (s === 'denied')
|
|
301
|
-
|
|
508
|
+
denied ??= r;
|
|
302
509
|
else if (s === 'grant')
|
|
303
|
-
|
|
510
|
+
granted ??= r;
|
|
304
511
|
}
|
|
305
|
-
if (
|
|
306
|
-
return true;
|
|
307
|
-
if (
|
|
308
|
-
return false;
|
|
512
|
+
if (granted !== undefined)
|
|
513
|
+
return { allowed: true, scope, via: 'principal', key: granted };
|
|
514
|
+
if (denied !== undefined)
|
|
515
|
+
return { allowed: false, scope, via: 'principal', key: denied };
|
|
516
|
+
}
|
|
517
|
+
// Tier 3 — the built-in `everyone` role (derived: a public plugin
|
|
518
|
+
// principal granted here counts, see `AccessScope.everyone`).
|
|
519
|
+
if (scope.everyone) {
|
|
520
|
+
return { allowed: scope.everyone === 'grant', scope, via: 'everyone', key: EVERYONE_CANONICAL };
|
|
309
521
|
}
|
|
310
|
-
// Tier 3 — the built-in `everyone` role.
|
|
311
|
-
const everyone = scope.byRole.get(EVERYONE_CANONICAL);
|
|
312
|
-
if (everyone)
|
|
313
|
-
return everyone === 'grant';
|
|
314
522
|
// No verdict at this scope — fall through to the next (farther) one.
|
|
315
523
|
}
|
|
316
|
-
|
|
524
|
+
// No verdict anywhere (and no root access.md to stop at): the floor still holds.
|
|
525
|
+
return adminFloor
|
|
526
|
+
? { allowed: true, scope: null, via: 'admin-floor' }
|
|
527
|
+
: { allowed: false, scope: null, via: 'default-deny' };
|
|
317
528
|
}
|
|
318
529
|
/**
|
|
319
530
|
* The `access.md` that a FOLDER target's own grants live in — its direct scope.
|
|
@@ -340,6 +551,39 @@ function scopeToGrantSource(scope, kind, relativePath) {
|
|
|
340
551
|
return { kind: 'direct' };
|
|
341
552
|
return { kind: 'ancestor', path };
|
|
342
553
|
}
|
|
554
|
+
/**
|
|
555
|
+
* Where a scope's rules live, as the place a person edits: a folder (the
|
|
556
|
+
* directory of its `access.md`) or the target's own frontmatter.
|
|
557
|
+
*/
|
|
558
|
+
function decisionSourceOf(scope, kind, relativePath) {
|
|
559
|
+
if (scope.source.kind === 'own')
|
|
560
|
+
return { kind: 'frontmatter', path: relativePath, inherited: false };
|
|
561
|
+
const folder = path.posix.dirname(scope.source.path);
|
|
562
|
+
const dir = folder === '.' ? '' : folder;
|
|
563
|
+
const grant = scopeToGrantSource(scope, kind, relativePath);
|
|
564
|
+
return { kind: 'folder', path: dir, inherited: grant.kind === 'ancestor' };
|
|
565
|
+
}
|
|
566
|
+
/** A resolver decision in the terms `explainAccess` reports. */
|
|
567
|
+
function explainDecision(model, decision, kind, relativePath) {
|
|
568
|
+
const source = decision.scope ? decisionSourceOf(decision.scope, kind, relativePath) : null;
|
|
569
|
+
const base = { allowed: decision.allowed, source };
|
|
570
|
+
switch (decision.via) {
|
|
571
|
+
case 'admin-rescue':
|
|
572
|
+
case 'default-deny':
|
|
573
|
+
return { ...base, via: decision.via, principal: null };
|
|
574
|
+
case 'admin-floor':
|
|
575
|
+
return { ...base, via: decision.via, principal: 'Admin' };
|
|
576
|
+
case 'email':
|
|
577
|
+
return { ...base, via: 'person', principal: decision.key ?? null };
|
|
578
|
+
case 'everyone':
|
|
579
|
+
return { ...base, via: 'everyone', principal: EVERYONE_CANONICAL };
|
|
580
|
+
case 'principal': {
|
|
581
|
+
const key = decision.key ?? '';
|
|
582
|
+
const record = model.roles.byCanonical.get(key);
|
|
583
|
+
return { ...base, via: record?.kind ?? 'role', principal: record?.displayName ?? key };
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
343
587
|
/**
|
|
344
588
|
* Resolve EVERY file scope that names a principal for `verb`, ordered
|
|
345
589
|
* closest-first — the source-returning twin of `hasPermissionResolved`, but
|
|
@@ -389,13 +633,7 @@ function resolveGrantSourcesForVerb(model, verb, kind, relativePath, principal,
|
|
|
389
633
|
// a GROUP whose group has VANISHED reads "unshadowed" here, and the
|
|
390
634
|
// alias-tolerant pair would then attribute a same-named role's
|
|
391
635
|
// surviving `role/<name>` grant to the group.
|
|
392
|
-
const
|
|
393
|
-
const explicit = canonical.startsWith(ROLE_TOKEN_PREFIX);
|
|
394
|
-
const bare = explicit ? canonical.slice(ROLE_TOKEN_PREFIX.length) : canonical;
|
|
395
|
-
const shadowed = model.roles.byCanonical.get(bare)?.kind === 'group';
|
|
396
|
-
const tokens = tokenMatch === 'exact' || shadowed
|
|
397
|
-
? [explicit ? `${ROLE_TOKEN_PREFIX}${bare}` : bare]
|
|
398
|
-
: [bare, `${ROLE_TOKEN_PREFIX}${bare}`];
|
|
636
|
+
const tokens = ownTokensOf(model, principal.role, tokenMatch);
|
|
399
637
|
for (const scope of scopes) {
|
|
400
638
|
const states = tokens.map((t) => scope.byRole.get(t));
|
|
401
639
|
if (states.includes('grant')) {
|
|
@@ -422,6 +660,69 @@ function resolveGrantSourcesForVerb(model, verb, kind, relativePath, principal,
|
|
|
422
660
|
}
|
|
423
661
|
return out;
|
|
424
662
|
}
|
|
663
|
+
/**
|
|
664
|
+
* WHICH token spellings in a file are THIS role-shaped principal's own entries
|
|
665
|
+
* — the shadowing rule, in one place because the grant walk, the denial walk
|
|
666
|
+
* and the revoke splice must all agree on it (a disagreement attributes a
|
|
667
|
+
* shadowed bare token to the wrong principal, which is how a "restrict" silently
|
|
668
|
+
* strips a same-named role's grant). See `resolveGrantSourcesForVerb` for the
|
|
669
|
+
* full reasoning behind each branch.
|
|
670
|
+
*/
|
|
671
|
+
function ownTokensOf(model, role, tokenMatch) {
|
|
672
|
+
const canonical = canonicalRoleName(role);
|
|
673
|
+
const explicit = canonical.startsWith(ROLE_TOKEN_PREFIX);
|
|
674
|
+
const bare = explicit ? canonical.slice(ROLE_TOKEN_PREFIX.length) : canonical;
|
|
675
|
+
const shadowed = model.roles.byCanonical.get(bare)?.kind === 'group';
|
|
676
|
+
return tokenMatch === 'exact' || shadowed
|
|
677
|
+
? [explicit ? `${ROLE_TOKEN_PREFIX}${bare}` : bare]
|
|
678
|
+
: [bare, `${ROLE_TOKEN_PREFIX}${bare}`];
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* The polarity twin of {@link resolveGrantSourcesForVerb}: every file scope
|
|
682
|
+
* whose own entry for this principal DENIES `verb`, closest-first.
|
|
683
|
+
*
|
|
684
|
+
* Same walk, mirrored stopping rule. A grant in the principal's own entry at a
|
|
685
|
+
* scope ENDS the list — under closest-wins a farther deny is dead, and reporting
|
|
686
|
+
* it would have the dialog say "restricted" about a verb the principal holds.
|
|
687
|
+
* A grant or deny reaching the principal through a group/role/`everyone` is not
|
|
688
|
+
* their own entry: it is neither a denial to report nor a reason to stop, so the
|
|
689
|
+
* walk carries on past it, exactly as the grant twin does.
|
|
690
|
+
*
|
|
691
|
+
* Returns `[]` when the principal is not denied `verb` by any file entry.
|
|
692
|
+
*/
|
|
693
|
+
function resolveDenialSourcesForVerb(model, verb, kind, relativePath, principal, fileOwn, tokenMatch) {
|
|
694
|
+
const scopes = resolveScopes(model, verb, relativePath, fileOwn);
|
|
695
|
+
const out = [];
|
|
696
|
+
if (principal.kind === 'role') {
|
|
697
|
+
const tokens = ownTokensOf(model, principal.role, tokenMatch);
|
|
698
|
+
for (const scope of scopes) {
|
|
699
|
+
const states = tokens.map((t) => scope.byRole.get(t));
|
|
700
|
+
// A grant under EITHER spelling wins within the scope (`buildScope` lets a
|
|
701
|
+
// grant stick over a deny of the same principal), so it ends the walk
|
|
702
|
+
// whether or not the other spelling denies here.
|
|
703
|
+
if (states.includes('grant'))
|
|
704
|
+
break;
|
|
705
|
+
// Only a `deny <verb>` LINE is a source; a denial the scope implies from
|
|
706
|
+
// a prerequisite verb's line (`deny write` when asking about owner) is
|
|
707
|
+
// real but is not this verb's entry — the prerequisite's own report is
|
|
708
|
+
// where it can be lifted.
|
|
709
|
+
const written = tokens.some((t) => scope.byRole.get(t) === 'denied' && !scope.implied.byRole.has(t));
|
|
710
|
+
if (written)
|
|
711
|
+
out.push(scopeToGrantSource(scope, kind, relativePath));
|
|
712
|
+
}
|
|
713
|
+
return out;
|
|
714
|
+
}
|
|
715
|
+
const email = canonicalEmail(principal.email);
|
|
716
|
+
for (const scope of scopes) {
|
|
717
|
+
const own = scope.byEmail.get(email);
|
|
718
|
+
if (own === 'grant')
|
|
719
|
+
break;
|
|
720
|
+
if (own === 'denied' && !scope.implied.byEmail.has(email)) {
|
|
721
|
+
out.push(scopeToGrantSource(scope, kind, relativePath));
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return out;
|
|
725
|
+
}
|
|
425
726
|
/**
|
|
426
727
|
* Build the repo-root → path chain of directory scopes that govern
|
|
427
728
|
* `relativePath`, e.g. `Knowledge/Sales/Foo.md` →
|
|
@@ -454,11 +755,11 @@ function dirChainFor(relativePath) {
|
|
|
454
755
|
* reported as restricted.
|
|
455
756
|
*/
|
|
456
757
|
function canEveryoneReadResolved(model, relativePath, fileOwn) {
|
|
457
|
-
const {
|
|
758
|
+
const { byEmail, everyone } = resolveAtPath(model, 'read', relativePath, fileOwn);
|
|
458
759
|
// Baseline: an unnamed signed-in user (no email/role entries) reads only via
|
|
459
760
|
// `everyone`. As a single principal, its collapsed verdict is its closest —
|
|
460
761
|
// exactly what that user resolves to.
|
|
461
|
-
if (
|
|
762
|
+
if (everyone !== 'grant')
|
|
462
763
|
return false;
|
|
463
764
|
// Every *named* principal must also still read. A collapsed deny may have
|
|
464
765
|
// been shadowed by a closer-scope `everyone` grant, so re-resolve each
|
|
@@ -490,8 +791,34 @@ function canEveryoneReadResolved(model, relativePath, fileOwn) {
|
|
|
490
791
|
function canReadResolved(model, userEmail, relativePath, fileOwn) {
|
|
491
792
|
return hasPermissionResolved(model, 'read', userEmail, relativePath, fileOwn);
|
|
492
793
|
}
|
|
794
|
+
/**
|
|
795
|
+
* The key set someone holding exactly the principal behind `token` carries —
|
|
796
|
+
* the question "would a member of this principal be allowed?" put to
|
|
797
|
+
* `hasPermissionForKeys`. A group gets its full member key set
|
|
798
|
+
* (`principalKeysOfGroup`); a role or plugin principal gets every key its
|
|
799
|
+
* record is registered under (the bare name and the `role/` alias share one
|
|
800
|
+
* record) plus the plugin principals expanded from those keys; a token with no
|
|
801
|
+
* record (`everyone`, a vanished principal) is just itself. Public keys are
|
|
802
|
+
* always included — every caller holds them.
|
|
803
|
+
*/
|
|
804
|
+
function principalKeysOfToken(model, token) {
|
|
805
|
+
const record = model.roles.byCanonical.get(token);
|
|
806
|
+
if (record?.kind === 'group')
|
|
807
|
+
return principalKeysOfGroup(model, token) ?? new Set([token]);
|
|
808
|
+
const keys = new Set([token]);
|
|
809
|
+
if (!record) {
|
|
810
|
+
for (const key of model.roles.publicKeys ?? [])
|
|
811
|
+
keys.add(key);
|
|
812
|
+
return keys;
|
|
813
|
+
}
|
|
814
|
+
for (const [key, principal] of model.roles.byCanonical) {
|
|
815
|
+
if (principal === record)
|
|
816
|
+
keys.add(key);
|
|
817
|
+
}
|
|
818
|
+
return addDerivedKeys(model, keys);
|
|
819
|
+
}
|
|
493
820
|
function eligibleHoldersResolved(model, verb, relativePath, fileOwn) {
|
|
494
|
-
const { byRole, byEmail } = resolveAtPath(model, verb, relativePath, fileOwn);
|
|
821
|
+
const { byRole, byEmail, everyone } = resolveAtPath(model, verb, relativePath, fileOwn);
|
|
495
822
|
// (kind, display name) → one entry. The `byRole` keys are the merged
|
|
496
823
|
// index's canonical tokens exactly as granted (bare, or the
|
|
497
824
|
// `role/<canonical>` alias); the `byCanonical` record a key hits carries
|
|
@@ -509,12 +836,34 @@ function eligibleHoldersResolved(model, verb, relativePath, fileOwn) {
|
|
|
509
836
|
if (!byIdentity.has(key))
|
|
510
837
|
byIdentity.set(key, { name, kind });
|
|
511
838
|
};
|
|
839
|
+
// Every row is re-checked against the resolver. The collapsed view keeps a
|
|
840
|
+
// principal's closest OWN verdict, but a nearer scope can still decide for
|
|
841
|
+
// them without naming them — a `deny everyone` in a subfolder cuts off a
|
|
842
|
+
// root `write: Admin` — and a list that named them anyway would tell the
|
|
843
|
+
// dialog and the denial message that someone is eligible whom the gate then
|
|
844
|
+
// refuses.
|
|
845
|
+
const allowedFor = (keys) => hasPermissionForKeys(model, verb, keys, relativePath, fileOwn);
|
|
512
846
|
for (const [canonical, state] of byRole) {
|
|
513
847
|
if (state !== 'grant')
|
|
514
848
|
continue;
|
|
849
|
+
if (!allowedFor(principalKeysOfToken(model, canonical)))
|
|
850
|
+
continue;
|
|
515
851
|
const record = model.roles.byCanonical.get(canonical);
|
|
516
852
|
addPrincipal(record ? record.displayName : canonical, record?.kind ?? 'role');
|
|
517
853
|
}
|
|
854
|
+
// The Admin write floor (see `hasPermissionResolved`): Admin holds write here
|
|
855
|
+
// whenever no nearer scope excludes it, whether or not any file names it.
|
|
856
|
+
if (verb === 'write' && model.roles.byCanonical.has(ADMIN_ROLE_KEY)) {
|
|
857
|
+
const adminRole = model.roles.byCanonical.get(ADMIN_ROLE_KEY);
|
|
858
|
+
if (allowedFor(principalKeysOfToken(model, ADMIN_ROLE_KEY)))
|
|
859
|
+
addPrincipal(adminRole.displayName, 'role');
|
|
860
|
+
}
|
|
861
|
+
// The derived everyone verdict: a public plugin principal granted here
|
|
862
|
+
// means anyone signed in holds the verb, and a list that counts holders (an
|
|
863
|
+
// approval gate, a "restricted to" banner) must say so — as `everyone`, the
|
|
864
|
+
// same row a literal grant produces.
|
|
865
|
+
if (everyone === 'grant' && allowedFor(NO_KEYS))
|
|
866
|
+
addPrincipal(EVERYONE_CANONICAL, 'role');
|
|
518
867
|
// Mirror the admin overrides applied in `hasPermissionResolved`: write on
|
|
519
868
|
// `roles.yaml` and on any `access.md` is granted to Admin even if the
|
|
520
869
|
// file content doesn't list it. Without surfacing that here, the
|
|
@@ -525,7 +874,7 @@ function eligibleHoldersResolved(model, verb, relativePath, fileOwn) {
|
|
|
525
874
|
// Look the Admin ROLE up via its explicit alias — the bare key may be
|
|
526
875
|
// owned by a same-named group under group-first precedence. The override
|
|
527
876
|
// is the ROLE's capability, so the row's kind is 'role' regardless.
|
|
528
|
-
const adminRole = model.roles.byCanonical.get(
|
|
877
|
+
const adminRole = model.roles.byCanonical.get(ADMIN_ROLE_KEY);
|
|
529
878
|
addPrincipal(adminRole ? adminRole.displayName : ADMIN_CANONICAL, 'role');
|
|
530
879
|
}
|
|
531
880
|
const principals = [...byIdentity.values()].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : a.kind < b.kind ? -1 : a.kind > b.kind ? 1 : 0);
|
|
@@ -537,11 +886,26 @@ function eligibleHoldersResolved(model, verb, relativePath, fileOwn) {
|
|
|
537
886
|
for (const [email, state] of byEmail) {
|
|
538
887
|
if (state !== 'grant')
|
|
539
888
|
continue;
|
|
889
|
+
if (!hasPermissionResolved(model, verb, email, relativePath, fileOwn))
|
|
890
|
+
continue;
|
|
540
891
|
users.push({ name: '', email });
|
|
541
892
|
}
|
|
542
893
|
users.sort((a, b) => a.email.localeCompare(b.email));
|
|
543
894
|
return { principals, roles, users };
|
|
544
895
|
}
|
|
896
|
+
/**
|
|
897
|
+
* Every individual the model names: `roles.yaml` members, emails named inline
|
|
898
|
+
* at this path's scope, and the deployment owner (Admin for the write floor
|
|
899
|
+
* even when `roles.yaml` does not list them).
|
|
900
|
+
*/
|
|
901
|
+
function namedCandidateEmails(model, byEmail) {
|
|
902
|
+
const candidates = new Set(model.deploymentOwners);
|
|
903
|
+
for (const email of model.roles.byEmail.keys())
|
|
904
|
+
candidates.add(email);
|
|
905
|
+
for (const email of byEmail.keys())
|
|
906
|
+
candidates.add(email);
|
|
907
|
+
return candidates;
|
|
908
|
+
}
|
|
545
909
|
/**
|
|
546
910
|
* Expand the eligible-writer set to the underlying emails.
|
|
547
911
|
*
|
|
@@ -561,12 +925,10 @@ function eligibleHolderEmailsResolved(model, verb, relativePath, fileOwn) {
|
|
|
561
925
|
// in an access.md entry at this path's scope (whether granted or denied —
|
|
562
926
|
// `hasPermissionResolved` filters denials out below). The built-in
|
|
563
927
|
// `everyone` role can grant arbitrary signed-in users, but this finite
|
|
564
|
-
// expansion can only return emails the access tree explicitly names.
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
for (const email of byEmail.keys())
|
|
569
|
-
candidates.add(email);
|
|
928
|
+
// expansion can only return emails the access tree explicitly names. The
|
|
929
|
+
// deployment owner is a candidate too: the Admin write floor admits them
|
|
930
|
+
// whether or not `roles.yaml` lists them.
|
|
931
|
+
const candidates = namedCandidateEmails(model, byEmail);
|
|
570
932
|
for (const email of candidates) {
|
|
571
933
|
if (hasPermissionResolved(model, verb, email, relativePath, fileOwn)) {
|
|
572
934
|
out.set(email, { name: '', email });
|
|
@@ -586,11 +948,7 @@ function eligibleHolderEmailsResolved(model, verb, relativePath, fileOwn) {
|
|
|
586
948
|
*/
|
|
587
949
|
function ineligibleNamedEmailsResolved(model, verb, relativePath, fileOwn) {
|
|
588
950
|
const { byEmail } = resolveAtPath(model, verb, relativePath, fileOwn);
|
|
589
|
-
const candidates =
|
|
590
|
-
for (const email of model.roles.byEmail.keys())
|
|
591
|
-
candidates.add(email);
|
|
592
|
-
for (const email of byEmail.keys())
|
|
593
|
-
candidates.add(email);
|
|
951
|
+
const candidates = namedCandidateEmails(model, byEmail);
|
|
594
952
|
const out = new Set();
|
|
595
953
|
for (const email of candidates) {
|
|
596
954
|
if (!hasPermissionResolved(model, verb, email, relativePath, fileOwn))
|
|
@@ -601,6 +959,8 @@ function ineligibleNamedEmailsResolved(model, verb, relativePath, fileOwn) {
|
|
|
601
959
|
export class AccessControlService {
|
|
602
960
|
workspaceService;
|
|
603
961
|
kbDirName;
|
|
962
|
+
disk;
|
|
963
|
+
gitRunner;
|
|
604
964
|
/**
|
|
605
965
|
* Per-workspace cache: `model` is the resolved access tree and `loadedAt`
|
|
606
966
|
* is when we computed it. We re-load on a TTL — the validator runs on
|
|
@@ -653,16 +1013,25 @@ export class AccessControlService {
|
|
|
653
1013
|
* environment, not from the knowledge base.
|
|
654
1014
|
*/
|
|
655
1015
|
deploymentOwners;
|
|
656
|
-
constructor(workspaceService, kbDirName,
|
|
1016
|
+
constructor(workspaceService, kbDirName, disk,
|
|
657
1017
|
/**
|
|
658
1018
|
* Emails that count as Admin for the two hardcoded `write` rescues,
|
|
659
1019
|
* whatever `roles.yaml` says — in practice `ADMIN_EMAIL`. Optional so the
|
|
660
1020
|
* many test fixtures that construct this service directly keep their
|
|
661
1021
|
* current behaviour (no owner, roles.yaml is the only authority).
|
|
662
1022
|
*/
|
|
663
|
-
deploymentOwners = []
|
|
1023
|
+
deploymentOwners = [],
|
|
1024
|
+
/**
|
|
1025
|
+
* How git is run — see `shared/git.contract.ts`. The composition root
|
|
1026
|
+
* passes the one runner; the default is the same runner on its default
|
|
1027
|
+
* deadline, kept for the same reason as `deploymentOwners` above: a great
|
|
1028
|
+
* many fixtures construct this service directly.
|
|
1029
|
+
*/
|
|
1030
|
+
gitRunner = new NodeGitRunner()) {
|
|
664
1031
|
this.workspaceService = workspaceService;
|
|
665
1032
|
this.kbDirName = kbDirName;
|
|
1033
|
+
this.disk = disk;
|
|
1034
|
+
this.gitRunner = gitRunner;
|
|
666
1035
|
this.deploymentOwners = new Set(deploymentOwners.filter(Boolean).map((e) => canonicalEmail(e)));
|
|
667
1036
|
}
|
|
668
1037
|
/**
|
|
@@ -674,7 +1043,29 @@ export class AccessControlService {
|
|
|
674
1043
|
invalidate(workspaceId) {
|
|
675
1044
|
this.cache.delete(workspaceId);
|
|
676
1045
|
this.ownEntriesCache.delete(workspaceId);
|
|
1046
|
+
this.generation++;
|
|
677
1047
|
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Bumped by every `invalidate()`, for any workspace. A model load reads the
|
|
1050
|
+
* tree across many awaits, and an invalidation fires exactly when that tree
|
|
1051
|
+
* changes underneath it: the load that started on the old tree would
|
|
1052
|
+
* otherwise store its model AFTER the drop, and every read for the next TTL
|
|
1053
|
+
* would resolve against a tree that is already gone. `loadModel` takes the
|
|
1054
|
+
* generation before its first read and refuses to store a model fetched
|
|
1055
|
+
* under an older one — the caller that started it still gets its answer,
|
|
1056
|
+
* as old as the moment it started, and the next caller reloads.
|
|
1057
|
+
*
|
|
1058
|
+
* ONE counter, not one per workspace: a per-workspace map would keep an
|
|
1059
|
+
* entry for every branch workspace the process ever loaded, with nothing to
|
|
1060
|
+
* tell it when a workspace is gone. The price of sharing is that a drop on
|
|
1061
|
+
* some other workspace, landing mid-load, costs this one a single reload —
|
|
1062
|
+
* a load is tens of milliseconds and a drop is a commit, so that is rare
|
|
1063
|
+
* and cheap, and it never stores a wrong model. (The own-entries memo needs
|
|
1064
|
+
* no token: it is keyed by the map object a reader took BEFORE its disk
|
|
1065
|
+
* read, and `invalidate()` replaces the map, so a late store lands in an
|
|
1066
|
+
* orphan nobody consults.)
|
|
1067
|
+
*/
|
|
1068
|
+
generation = 0;
|
|
678
1069
|
/** Memoized `readOwnEntries` for the batch paths; see `ownEntriesCache`. */
|
|
679
1070
|
async cachedOwnEntries(workspaceId, repoDir, relativePath) {
|
|
680
1071
|
let ws = this.ownEntriesCache.get(workspaceId);
|
|
@@ -727,6 +1118,35 @@ export class AccessControlService {
|
|
|
727
1118
|
});
|
|
728
1119
|
return result;
|
|
729
1120
|
}
|
|
1121
|
+
async canReadAsGroupBatch(workspaceId, group, relativePaths) {
|
|
1122
|
+
const model = await this.loadModel(workspaceId);
|
|
1123
|
+
const keys = principalKeysOfGroup(model, group);
|
|
1124
|
+
if (keys === null)
|
|
1125
|
+
return null;
|
|
1126
|
+
const repoDir = await this.repoDir(workspaceId);
|
|
1127
|
+
// The same per-path own-entries `canReadBatch` folds in: a file's own
|
|
1128
|
+
// frontmatter rules are part of the walk for a group as for a person.
|
|
1129
|
+
const owns = await Promise.all(relativePaths.map((p) => this.cachedOwnEntries(workspaceId, repoDir, p)));
|
|
1130
|
+
const result = new Map();
|
|
1131
|
+
relativePaths.forEach((p, i) => {
|
|
1132
|
+
result.set(p, hasPermissionForKeys(model, 'read', keys, p, owns[i]));
|
|
1133
|
+
});
|
|
1134
|
+
return result;
|
|
1135
|
+
}
|
|
1136
|
+
async canReadAsEveryoneBatch(workspaceId, relativePaths) {
|
|
1137
|
+
const model = await this.loadModel(workspaceId);
|
|
1138
|
+
const repoDir = await this.repoDir(workspaceId);
|
|
1139
|
+
const owns = await Promise.all(relativePaths.map((p) => this.cachedOwnEntries(workspaceId, repoDir, p)));
|
|
1140
|
+
const result = new Map();
|
|
1141
|
+
relativePaths.forEach((p, i) => {
|
|
1142
|
+
// No keys at all: the walk then falls through to each scope's derived
|
|
1143
|
+
// `everyone` verdict — the built-in entry, or a public plugin's grant
|
|
1144
|
+
// or denial, whichever the scope builder settled on — which is exactly
|
|
1145
|
+
// what a caller holding nothing of their own resolves to.
|
|
1146
|
+
result.set(p, hasPermissionForKeys(model, 'read', NO_KEYS, p, owns[i]));
|
|
1147
|
+
});
|
|
1148
|
+
return result;
|
|
1149
|
+
}
|
|
730
1150
|
async canWriteBatch(workspaceId, userEmail, relativePaths) {
|
|
731
1151
|
const model = await this.loadModel(workspaceId);
|
|
732
1152
|
const repoDir = await this.repoDir(workspaceId);
|
|
@@ -773,20 +1193,71 @@ export class AccessControlService {
|
|
|
773
1193
|
async eligibleReaders(workspaceId, relativePath) {
|
|
774
1194
|
const model = await this.loadModel(workspaceId);
|
|
775
1195
|
const own = await this.readOwnEntries(await this.repoDir(workspaceId), relativePath);
|
|
776
|
-
//
|
|
777
|
-
// and the
|
|
778
|
-
//
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
1196
|
+
// `restricted` is the verdict — whether `read: everyone` applies cleanly —
|
|
1197
|
+
// and the lists are the GRANTS that exist regardless: on a public node
|
|
1198
|
+
// they name what makes it public (`everyone`, and a public plugin
|
|
1199
|
+
// principal whose grant is the one to remove), so the share dialog can
|
|
1200
|
+
// show that grant as a row. Consumers that only want to know whether the
|
|
1201
|
+
// node is public read the flag; the lists may be empty for a
|
|
1202
|
+
// default-denied path with no grants.
|
|
782
1203
|
const { principals, roles, users } = eligibleHoldersResolved(model, 'read', relativePath, own);
|
|
783
|
-
|
|
1204
|
+
// WHY it is public, when it is: the public plugin principals granted read
|
|
1205
|
+
// here — the one thing the dialog cannot tell from the lists alone (a
|
|
1206
|
+
// plugin principal in them may or may not be public), so the resolver,
|
|
1207
|
+
// which knows, says it. A literal `everyone` line shows up as a source
|
|
1208
|
+
// of the `everyone` row instead; together the two answer "what remains
|
|
1209
|
+
// public after this grant is removed".
|
|
1210
|
+
const collapsed = resolveAtPath(model, 'read', relativePath, own);
|
|
1211
|
+
const publicVia = [...collapsed.byRole]
|
|
1212
|
+
.filter(([key, state]) => state === 'grant' && model.roles.publicKeys?.has(key))
|
|
1213
|
+
.map(([key]) => key)
|
|
1214
|
+
.sort();
|
|
1215
|
+
return { restricted: !canEveryoneReadResolved(model, relativePath, own), principals, roles, users, publicVia };
|
|
784
1216
|
}
|
|
785
1217
|
async eligibleDownloaders(workspaceId, relativePath) {
|
|
786
1218
|
const model = await this.loadModel(workspaceId);
|
|
787
1219
|
const own = await this.readOwnEntries(await this.repoDir(workspaceId), relativePath);
|
|
788
1220
|
return eligibleHoldersResolved(model, 'download', relativePath, own);
|
|
789
1221
|
}
|
|
1222
|
+
/**
|
|
1223
|
+
* The read and write holders of one file at its current path and at a path
|
|
1224
|
+
* it has not moved to yet (see the interface).
|
|
1225
|
+
*
|
|
1226
|
+
* A move changes nothing about the file's own frontmatter — that travels
|
|
1227
|
+
* with the bytes — and everything about the folder chain above it. So the
|
|
1228
|
+
* hypothetical side is the ordinary resolution with the destination path
|
|
1229
|
+
* substituted: the same model, the same own-entries (read from the SOURCE,
|
|
1230
|
+
* since nothing sits at the destination to read), resolved against the
|
|
1231
|
+
* destination's ancestors. One model load and one file read serve all four
|
|
1232
|
+
* lookups.
|
|
1233
|
+
*/
|
|
1234
|
+
async prospectiveHolders(workspaceId, fromPath, toPath) {
|
|
1235
|
+
const model = await this.loadModel(workspaceId);
|
|
1236
|
+
const repoDir = await this.repoDir(workspaceId);
|
|
1237
|
+
// A FILE question only. A folder carries its own `access.md` — which moves
|
|
1238
|
+
// with it and governs everything under it — so resolving it as a file
|
|
1239
|
+
// would read frontmatter it does not have and omit the rules it does,
|
|
1240
|
+
// naming principals that are not the ones a folder move changes. Refuse
|
|
1241
|
+
// rather than answer the wrong question convincingly.
|
|
1242
|
+
//
|
|
1243
|
+
// Only absence is an answer here (see `isAbsence`): a probe that failed on
|
|
1244
|
+
// permissions or I/O does not say "this is a file", and folding it into
|
|
1245
|
+
// one would let the very case above through on an unreadable source.
|
|
1246
|
+
const fromStat = await fs.stat(path.join(repoDir, fromPath)).catch((err) => {
|
|
1247
|
+
if (isAbsence(err))
|
|
1248
|
+
return null;
|
|
1249
|
+
throw err;
|
|
1250
|
+
});
|
|
1251
|
+
if (fromStat?.isDirectory()) {
|
|
1252
|
+
throw new WorkflowDomainError('prospective access answers for a file, not a folder', 400);
|
|
1253
|
+
}
|
|
1254
|
+
const own = await this.readOwnEntries(repoDir, fromPath);
|
|
1255
|
+
const holdersAt = (relativePath) => ({
|
|
1256
|
+
read: eligibleHoldersResolved(model, 'read', relativePath, own),
|
|
1257
|
+
write: eligibleHoldersResolved(model, 'write', relativePath, own),
|
|
1258
|
+
});
|
|
1259
|
+
return { before: holdersAt(fromPath), after: holdersAt(toPath) };
|
|
1260
|
+
}
|
|
790
1261
|
async eligibleWriterEmails(workspaceId, relativePath) {
|
|
791
1262
|
const model = await this.loadModel(workspaceId);
|
|
792
1263
|
const own = await this.readOwnEntries(await this.repoDir(workspaceId), relativePath);
|
|
@@ -813,13 +1284,92 @@ export class AccessControlService {
|
|
|
813
1284
|
}
|
|
814
1285
|
return out;
|
|
815
1286
|
}
|
|
1287
|
+
async denialSources(workspaceId, kind, relativePath, principal, opts) {
|
|
1288
|
+
const model = await this.loadModel(workspaceId);
|
|
1289
|
+
// Same own-scope rule as `grantSources`: a file target consults its own
|
|
1290
|
+
// frontmatter, a folder target's own access.md is already in the dir chain.
|
|
1291
|
+
const own = kind === 'file'
|
|
1292
|
+
? await this.readOwnEntries(await this.repoDir(workspaceId), relativePath)
|
|
1293
|
+
: null;
|
|
1294
|
+
const out = {};
|
|
1295
|
+
for (const verb of KNOWN_VERBS) {
|
|
1296
|
+
const sources = resolveDenialSourcesForVerb(model, verb, kind, relativePath, principal, own, opts?.tokenMatch);
|
|
1297
|
+
if (sources.length > 0)
|
|
1298
|
+
out[verb] = sources;
|
|
1299
|
+
}
|
|
1300
|
+
return out;
|
|
1301
|
+
}
|
|
1302
|
+
async locallyDeniedPrincipals(workspaceId, kind, relativePath) {
|
|
1303
|
+
const model = await this.loadModel(workspaceId);
|
|
1304
|
+
// The target's OWN entries, and only those — the one scope whose rules a
|
|
1305
|
+
// restriction made here is written into. A folder's are its `access.md` in
|
|
1306
|
+
// the dir model; a file node's are its own frontmatter.
|
|
1307
|
+
const own = kind === 'folder'
|
|
1308
|
+
? (model.accessFilesByDir.get(relativePath)?.entries ?? null)
|
|
1309
|
+
: await this.readOwnEntries(await this.repoDir(workspaceId), relativePath);
|
|
1310
|
+
if (!own)
|
|
1311
|
+
return { principals: [], users: [] };
|
|
1312
|
+
const byIdentity = new Map();
|
|
1313
|
+
const byEmail = new Map();
|
|
1314
|
+
for (const verb of KNOWN_VERBS) {
|
|
1315
|
+
for (const entry of own[verb] ?? []) {
|
|
1316
|
+
// Grants are already reported by the eligible lists; only the denials
|
|
1317
|
+
// are invisible there, and only they are this method's business.
|
|
1318
|
+
if (!entry.deny)
|
|
1319
|
+
continue;
|
|
1320
|
+
if (entry.kind === 'user') {
|
|
1321
|
+
if (!byEmail.has(entry.email)) {
|
|
1322
|
+
byEmail.set(entry.email, { name: entry.displayName, email: entry.email });
|
|
1323
|
+
}
|
|
1324
|
+
continue;
|
|
1325
|
+
}
|
|
1326
|
+
// A token naming a role nobody knows is not a rule: `loadModel` drops
|
|
1327
|
+
// such entries from every `access.md`, and `resolveScopes` filters them
|
|
1328
|
+
// out of a file's own frontmatter (the one scope it does not pre-filter)
|
|
1329
|
+
// — so the resolver denies nothing, `denialSources` reports nothing, and
|
|
1330
|
+
// a row here would claim a restriction the server does not enforce. Skip
|
|
1331
|
+
// it on the same rule, so this list and the denial sources agree.
|
|
1332
|
+
if (!roleKnown(model.roles, entry.role))
|
|
1333
|
+
continue;
|
|
1334
|
+
// Resolve the token's kind and display name through the SAME merged
|
|
1335
|
+
// index the eligible lists use, so a denied group keys as `g:` and a
|
|
1336
|
+
// denied role as `r:` — matching the row keys the view is built on. A
|
|
1337
|
+
// KNOWN token with no record is a built-in (`everyone`, `admin` before
|
|
1338
|
+
// roles.yaml names it), which displays as the role it is.
|
|
1339
|
+
const record = model.roles.byCanonical.get(entry.role);
|
|
1340
|
+
const name = record ? record.displayName : (entry.displayRole || entry.role);
|
|
1341
|
+
const kindOf = record?.kind ?? 'role';
|
|
1342
|
+
const key = `${kindOf}\0${name.toLowerCase()}`;
|
|
1343
|
+
if (!byIdentity.has(key))
|
|
1344
|
+
byIdentity.set(key, { name, kind: kindOf });
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
const principals = [...byIdentity.values()].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : a.kind < b.kind ? -1 : a.kind > b.kind ? 1 : 0);
|
|
1348
|
+
const users = [...byEmail.values()].sort((a, b) => a.email.localeCompare(b.email));
|
|
1349
|
+
return { principals, users };
|
|
1350
|
+
}
|
|
1351
|
+
async explainAccess(workspaceId, userEmail, kind, relativePath) {
|
|
1352
|
+
const model = await this.loadModel(workspaceId);
|
|
1353
|
+
// The same own-entries read `canRead`/`canWrite`/… make, whatever the
|
|
1354
|
+
// kind: a directory simply has none.
|
|
1355
|
+
const own = await this.readOwnEntries(await this.repoDir(workspaceId), relativePath);
|
|
1356
|
+
const out = {};
|
|
1357
|
+
for (const verb of KNOWN_VERBS) {
|
|
1358
|
+
const machineOwned = verb === 'write' ? this.machineOwnedWriteRule(userEmail, relativePath) : null;
|
|
1359
|
+
out[verb] =
|
|
1360
|
+
machineOwned !== null
|
|
1361
|
+
? { allowed: machineOwned, source: null, via: 'machine-owned', principal: null }
|
|
1362
|
+
: explainDecision(model, decidePermissionResolved(model, verb, userEmail, relativePath, own), kind, relativePath);
|
|
1363
|
+
}
|
|
1364
|
+
return out;
|
|
1365
|
+
}
|
|
816
1366
|
async kbPrincipals(workspaceId) {
|
|
817
1367
|
let model;
|
|
818
1368
|
try {
|
|
819
1369
|
model = await this.loadModel(workspaceId);
|
|
820
1370
|
}
|
|
821
1371
|
catch {
|
|
822
|
-
return { roles: [], groups: [], people: [] };
|
|
1372
|
+
return { roles: [], groups: [], plugins: [], people: [] };
|
|
823
1373
|
}
|
|
824
1374
|
// Roles = the built-in `everyone` role plus every declared role's display
|
|
825
1375
|
// name — ROLE principals only, never groups. Each role is enumerated via
|
|
@@ -831,11 +1381,22 @@ export class AccessControlService {
|
|
|
831
1381
|
// direct-access.md edit).
|
|
832
1382
|
const roles = [EVERYONE_DISPLAY];
|
|
833
1383
|
const groups = [];
|
|
1384
|
+
// Plugins = the identities behind the synthesised `plugin/<name>/<verb>`
|
|
1385
|
+
// principals, once each (three keys share a folder). Personal shelves are
|
|
1386
|
+
// plugins to the resolver but not to a person picking a grantee — told
|
|
1387
|
+
// apart by their FOLDER (the one structural rule), never by their name.
|
|
1388
|
+
const plugins = new Map();
|
|
834
1389
|
for (const [key, principal] of model.roles.byCanonical) {
|
|
835
1390
|
if (key.startsWith(ROLE_TOKEN_PREFIX))
|
|
836
1391
|
roles.push(principal.displayName);
|
|
837
1392
|
else if (principal.kind === 'group')
|
|
838
1393
|
groups.push(principal.displayName);
|
|
1394
|
+
else if (principal.kind === 'plugin' &&
|
|
1395
|
+
principal.pluginName &&
|
|
1396
|
+
principal.pluginDir &&
|
|
1397
|
+
!isPersonalPluginDir(principal.pluginDir)) {
|
|
1398
|
+
plugins.set(principal.pluginName, principal.pluginDir);
|
|
1399
|
+
}
|
|
839
1400
|
}
|
|
840
1401
|
// People = roles.yaml member emails (name-less) ∪ access.md `Name <email>`
|
|
841
1402
|
// grants (named). The login-only users table is unioned in by the caller.
|
|
@@ -857,7 +1418,14 @@ export class AccessControlService {
|
|
|
857
1418
|
name: name || email.split('@')[0],
|
|
858
1419
|
email,
|
|
859
1420
|
}));
|
|
860
|
-
return {
|
|
1421
|
+
return {
|
|
1422
|
+
roles,
|
|
1423
|
+
groups,
|
|
1424
|
+
plugins: [...plugins.entries()]
|
|
1425
|
+
.map(([name, folder]) => ({ name, folder }))
|
|
1426
|
+
.sort((a, b) => a.name.localeCompare(b.name)),
|
|
1427
|
+
people,
|
|
1428
|
+
};
|
|
861
1429
|
}
|
|
862
1430
|
async findEmailByHash(workspaceId, hash) {
|
|
863
1431
|
let model;
|
|
@@ -887,7 +1455,7 @@ export class AccessControlService {
|
|
|
887
1455
|
...namesByEmail.keys(),
|
|
888
1456
|
]);
|
|
889
1457
|
for (const email of candidates) {
|
|
890
|
-
if (
|
|
1458
|
+
if (hashEmail(email) === hash) {
|
|
891
1459
|
const displayName = namesByEmail.get(email) ?? email.split('@')[0];
|
|
892
1460
|
return { email, displayName };
|
|
893
1461
|
}
|
|
@@ -906,7 +1474,7 @@ export class AccessControlService {
|
|
|
906
1474
|
machineOwnedWriteRule(userEmail, relativePath) {
|
|
907
1475
|
if (relativePath !== SYNCED_GROUPS_YAML)
|
|
908
1476
|
return null;
|
|
909
|
-
return userEmail
|
|
1477
|
+
return canonicalEmail(userEmail) === DIRECTORY_SYNC_BOT_EMAIL;
|
|
910
1478
|
}
|
|
911
1479
|
async canWriteAtRef(workspaceId, ref, userEmail, relativePath) {
|
|
912
1480
|
const machineOwned = this.machineOwnedWriteRule(userEmail, relativePath);
|
|
@@ -967,6 +1535,50 @@ export class AccessControlService {
|
|
|
967
1535
|
const own = await this.readOwnEntriesAtRef(repoDir, loaded.resolvedRef, relativePath);
|
|
968
1536
|
return eligibleHoldersResolved(loaded.model, 'write', relativePath, own);
|
|
969
1537
|
}
|
|
1538
|
+
async holdsAdminRootWrite(workspaceId, userEmail) {
|
|
1539
|
+
const model = await this.loadModel(workspaceId);
|
|
1540
|
+
return isAdminEmail(model, canonicalEmail(userEmail));
|
|
1541
|
+
}
|
|
1542
|
+
async canRestorePlatformFile(workspaceId, userEmail, destinationRelativePath) {
|
|
1543
|
+
const target = platformRestoreDestination(destinationRelativePath);
|
|
1544
|
+
if (target === null)
|
|
1545
|
+
return false;
|
|
1546
|
+
const email = canonicalEmail(userEmail);
|
|
1547
|
+
let admin;
|
|
1548
|
+
try {
|
|
1549
|
+
admin = isAdminEmail(await this.loadModel(workspaceId), email);
|
|
1550
|
+
}
|
|
1551
|
+
catch {
|
|
1552
|
+
// The repository this rescue exists for is exactly the one whose
|
|
1553
|
+
// `roles.yaml` may be the file that went missing, and `loadModel`
|
|
1554
|
+
// throws when it cannot be read. With no model to ask, the deployment
|
|
1555
|
+
// owner is the only admin left — and they are the person who sets
|
|
1556
|
+
// `ADMIN_EMAIL`, so admitting them concedes nothing they did not have.
|
|
1557
|
+
admin = this.deploymentOwners.has(email);
|
|
1558
|
+
}
|
|
1559
|
+
if (!admin)
|
|
1560
|
+
return false;
|
|
1561
|
+
// A restore puts back what is MISSING, so the place it lands must be
|
|
1562
|
+
// empty. An `access.md` goes back only into a folder that has none —
|
|
1563
|
+
// into a folder that already has one it would not be a restore but a rule
|
|
1564
|
+
// change wearing a move's clothes, and the destination's own rules would
|
|
1565
|
+
// be the thing it bypassed the write gate to overwrite. The same holds at
|
|
1566
|
+
// the root: a move is a rename on disk, so landing `.bevelignore` on a
|
|
1567
|
+
// root that already has one would silently replace it, and the repository
|
|
1568
|
+
// was never missing that file to begin with.
|
|
1569
|
+
const destination = target.kind === 'root' ? target.name : path.join(target.dir, 'access.md');
|
|
1570
|
+
const repoDir = await this.repoDir(workspaceId);
|
|
1571
|
+
try {
|
|
1572
|
+
await fs.stat(path.join(repoDir, destination));
|
|
1573
|
+
return false;
|
|
1574
|
+
}
|
|
1575
|
+
catch (err) {
|
|
1576
|
+
if (isAbsence(err))
|
|
1577
|
+
return true;
|
|
1578
|
+
// Anything other than genuine absence is not an answer: fail closed.
|
|
1579
|
+
return false;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
970
1582
|
async eligibleWritersForPathsAtRef(workspaceId, ref, relativePaths) {
|
|
971
1583
|
const result = new Map();
|
|
972
1584
|
// Machine-owned paths resolve without the model — same answer
|
|
@@ -1012,6 +1624,8 @@ export class AccessControlService {
|
|
|
1012
1624
|
if (cached && Date.now() - cached.loadedAt < AccessControlService.CACHE_TTL_MS) {
|
|
1013
1625
|
return cached.model;
|
|
1014
1626
|
}
|
|
1627
|
+
// Taken before the first read; see `generation`.
|
|
1628
|
+
const generation = this.generation;
|
|
1015
1629
|
const repoDir = await this.repoDir(workspaceId);
|
|
1016
1630
|
let rolesYaml;
|
|
1017
1631
|
try {
|
|
@@ -1035,20 +1649,19 @@ export class AccessControlService {
|
|
|
1035
1649
|
return await fs.readFile(path.join(repoDir, filename), 'utf-8');
|
|
1036
1650
|
}
|
|
1037
1651
|
catch (err) {
|
|
1038
|
-
|
|
1039
|
-
if (code === 'ENOENT' || code === 'ENOTDIR')
|
|
1652
|
+
if (isAbsence(err))
|
|
1040
1653
|
return null;
|
|
1041
1654
|
throw err;
|
|
1042
1655
|
}
|
|
1043
1656
|
});
|
|
1044
1657
|
if (!activeGroups.health.ok) {
|
|
1045
|
-
|
|
1658
|
+
log.error(`groups source ${activeGroups.health.file} is broken (${activeGroups.health.reason}) — groups contribute nothing until it is fixed`);
|
|
1046
1659
|
}
|
|
1047
1660
|
for (const w of activeGroups.warnings)
|
|
1048
|
-
|
|
1661
|
+
log.warn(w);
|
|
1049
1662
|
const mergeWarnings = mergeGroupsIntoRoles(rolesParsed.index, activeGroups.groups, activeGroups.sourceFile);
|
|
1050
1663
|
for (const w of mergeWarnings)
|
|
1051
|
-
|
|
1664
|
+
log.warn(w);
|
|
1052
1665
|
const accessFiles = new Map();
|
|
1053
1666
|
// Walk the entire repo for `access.md` files. The access tree is
|
|
1054
1667
|
// structure-agnostic — any `access.md` at any depth (including repo
|
|
@@ -1063,7 +1676,14 @@ export class AccessControlService {
|
|
|
1063
1676
|
// must not 500 the entire editor; admins can still write `access.md`
|
|
1064
1677
|
// / `roles.yaml` because `hasPermissionResolved` admin-rescues those
|
|
1065
1678
|
// paths, so the bad config remains fixable from inside the app.
|
|
1066
|
-
|
|
1679
|
+
const pluginDirs = new Map();
|
|
1680
|
+
await this.disk.walkKb(repoDir, [accessFileListener(repoDir, accessFiles, pluginDirs)]);
|
|
1681
|
+
// Plugin principals (`plugin/<Name>/<verb>`), derived from each plugin
|
|
1682
|
+
// folder's own access.md — a plugin being a folder with a manifest, at
|
|
1683
|
+
// any depth — after groups merged (so their rosters expand through the
|
|
1684
|
+
// finished index) and before the unknown-role sweep below (so a grant
|
|
1685
|
+
// naming one counts as known).
|
|
1686
|
+
synthesizePluginPrincipals(rolesParsed.index, accessFiles, pluginDirs);
|
|
1067
1687
|
// Validate role refs against roles.yaml. `everyone` is a built-in role and
|
|
1068
1688
|
// is valid without a roles.yaml entry. Unknown refs are dropped from the
|
|
1069
1689
|
// parsed entry list (along with a warn log) so the rest of the file still
|
|
@@ -1072,7 +1692,7 @@ export class AccessControlService {
|
|
|
1072
1692
|
for (const verb of KNOWN_VERBS) {
|
|
1073
1693
|
file.entries[verb] = file.entries[verb].filter((entry) => {
|
|
1074
1694
|
if (entry.kind === 'role' && !roleKnown(rolesParsed.index, entry.role)) {
|
|
1075
|
-
|
|
1695
|
+
log.warn(`${file.path}: '${verb}' references unknown role '${entry.displayRole}' — entry ignored`);
|
|
1076
1696
|
return false;
|
|
1077
1697
|
}
|
|
1078
1698
|
return true;
|
|
@@ -1085,49 +1705,12 @@ export class AccessControlService {
|
|
|
1085
1705
|
groupsHealth: activeGroups.health,
|
|
1086
1706
|
deploymentOwners: this.deploymentOwners,
|
|
1087
1707
|
};
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
let entries;
|
|
1093
|
-
try {
|
|
1094
|
-
entries = await fs.readdir(absDir, { withFileTypes: true });
|
|
1095
|
-
}
|
|
1096
|
-
catch {
|
|
1097
|
-
return;
|
|
1098
|
-
}
|
|
1099
|
-
for (const entry of entries) {
|
|
1100
|
-
// Skip VCS metadata + vendored deps. Hidden dirs (`.git`, `.vscode`,
|
|
1101
|
-
// etc.) and `node_modules` can't host KB rules and are often huge —
|
|
1102
|
-
// walking them would slow every cache miss without benefit.
|
|
1103
|
-
if (entry.name.startsWith('.'))
|
|
1104
|
-
continue;
|
|
1105
|
-
if (entry.name === 'node_modules')
|
|
1106
|
-
continue;
|
|
1107
|
-
const abs = path.join(absDir, entry.name);
|
|
1108
|
-
const rel = relDir ? `${relDir}/${entry.name}` : entry.name;
|
|
1109
|
-
if (entry.isDirectory()) {
|
|
1110
|
-
await this.collectAccessFiles(abs, rel, out);
|
|
1111
|
-
}
|
|
1112
|
-
else if (entry.isFile() && entry.name === 'access.md') {
|
|
1113
|
-
const text = await fs.readFile(abs, 'utf-8');
|
|
1114
|
-
const parsed = parseAccessFile(text, rel);
|
|
1115
|
-
if (!parsed.ok) {
|
|
1116
|
-
// Treat as if the file didn't exist for resolution purposes.
|
|
1117
|
-
// Admin-rescue on access.md paths still lets an admin fix it.
|
|
1118
|
-
for (const e of parsed.errors)
|
|
1119
|
-
console.warn(`[access] ${e} — file ignored`);
|
|
1120
|
-
continue;
|
|
1121
|
-
}
|
|
1122
|
-
for (const w of parsed.warnings)
|
|
1123
|
-
console.warn(`[access] ${w}`);
|
|
1124
|
-
if (out.has(parsed.file.dir)) {
|
|
1125
|
-
console.warn(`[access] ${rel}: duplicate access.md for directory '${parsed.file.dir}' — keeping the first one seen`);
|
|
1126
|
-
continue;
|
|
1127
|
-
}
|
|
1128
|
-
out.set(parsed.file.dir, parsed.file);
|
|
1129
|
-
}
|
|
1708
|
+
// An invalidation landed mid-load: this model describes a tree that is
|
|
1709
|
+
// already gone, so it is returned to this caller but never stored.
|
|
1710
|
+
if (this.generation === generation) {
|
|
1711
|
+
this.cache.set(workspaceId, { model, loadedAt: Date.now() });
|
|
1130
1712
|
}
|
|
1713
|
+
return model;
|
|
1131
1714
|
}
|
|
1132
1715
|
async repoDir(workspaceId) {
|
|
1133
1716
|
const wsDir = await this.workspaceService.getWorkspacePath(workspaceId);
|
|
@@ -1190,7 +1773,7 @@ export class AccessControlService {
|
|
|
1190
1773
|
}
|
|
1191
1774
|
if (wanted.length === 0)
|
|
1192
1775
|
return result;
|
|
1193
|
-
const texts = await catFileBatch(repoDir, wanted.map((p) => `${ref}:${p}`));
|
|
1776
|
+
const texts = await catFileBatch(this.gitRunner, repoDir, wanted.map((p) => `${ref}:${p}`));
|
|
1194
1777
|
wanted.forEach((p, i) => {
|
|
1195
1778
|
const text = texts[i];
|
|
1196
1779
|
if (text === null)
|
|
@@ -1230,11 +1813,7 @@ export class AccessControlService {
|
|
|
1230
1813
|
*/
|
|
1231
1814
|
async showAtRefOutcome(repoDir, ref, relativePath) {
|
|
1232
1815
|
try {
|
|
1233
|
-
const { stdout } = await
|
|
1234
|
-
encoding: 'utf-8',
|
|
1235
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
1236
|
-
env: { ...process.env, LC_ALL: 'C', LANG: 'C' },
|
|
1237
|
-
});
|
|
1816
|
+
const { stdout } = await this.gitRunner.run(repoDir, ['show', `${ref}:${relativePath}`]);
|
|
1238
1817
|
return { kind: 'text', text: stdout };
|
|
1239
1818
|
}
|
|
1240
1819
|
catch (err) {
|
|
@@ -1253,16 +1832,20 @@ export class AccessControlService {
|
|
|
1253
1832
|
*/
|
|
1254
1833
|
async listAccessFilesAtRef(repoDir, ref) {
|
|
1255
1834
|
try {
|
|
1256
|
-
const { stdout } = await
|
|
1835
|
+
const { stdout } = await this.gitRunner.run(repoDir, ['ls-tree', '-r', '--name-only', ref]);
|
|
1257
1836
|
const out = [];
|
|
1837
|
+
const pluginDirs = [];
|
|
1258
1838
|
for (const line of stdout.split('\n')) {
|
|
1259
1839
|
const p = line.trim();
|
|
1260
1840
|
if (!p)
|
|
1261
1841
|
continue;
|
|
1262
1842
|
if (p === 'access.md' || p.endsWith('/access.md'))
|
|
1263
1843
|
out.push(p);
|
|
1844
|
+
else if (p.startsWith(`${PLUGINS_DIR}/`) && p.endsWith(`/${PLUGIN_MANIFEST_FILE}`)) {
|
|
1845
|
+
pluginDirs.push(p.slice(0, -(PLUGIN_MANIFEST_FILE.length + 1)));
|
|
1846
|
+
}
|
|
1264
1847
|
}
|
|
1265
|
-
return out;
|
|
1848
|
+
return { accessFiles: out, pluginDirs };
|
|
1266
1849
|
}
|
|
1267
1850
|
catch {
|
|
1268
1851
|
return 'error';
|
|
@@ -1338,11 +1921,21 @@ export class AccessControlService {
|
|
|
1338
1921
|
if (!ref || ref.startsWith('-'))
|
|
1339
1922
|
return null;
|
|
1340
1923
|
try {
|
|
1341
|
-
const { stdout } = await
|
|
1924
|
+
const { stdout } = await this.gitRunner.run(repoDir, [
|
|
1925
|
+
'rev-parse',
|
|
1926
|
+
'--verify',
|
|
1927
|
+
'--quiet',
|
|
1928
|
+
`${ref}^{commit}`,
|
|
1929
|
+
]);
|
|
1342
1930
|
const sha = stdout.trim();
|
|
1343
1931
|
return /^[0-9a-f]{40,64}$/.test(sha) ? sha : null;
|
|
1344
1932
|
}
|
|
1345
|
-
catch {
|
|
1933
|
+
catch (err) {
|
|
1934
|
+
// Null means "does not resolve". A deadline says nothing about the ref,
|
|
1935
|
+
// and as a null it would turn a stalled git into a gate that quietly
|
|
1936
|
+
// reads no roles at that ref; it surfaces instead, as a read failure.
|
|
1937
|
+
if (isGitTimeout(err))
|
|
1938
|
+
throw err;
|
|
1346
1939
|
return null;
|
|
1347
1940
|
}
|
|
1348
1941
|
}
|
|
@@ -1374,7 +1967,7 @@ export class AccessControlService {
|
|
|
1374
1967
|
if (outcome.kind === 'error') {
|
|
1375
1968
|
// The operational signal: one line per failed read, naming the commit
|
|
1376
1969
|
// and the path, so a run of these is visible in the logs.
|
|
1377
|
-
|
|
1970
|
+
logger(`access@${tag}`).warn(`git read of ${relativePath} failed; refusing to decide from a partial tree`);
|
|
1378
1971
|
throw new AccessUnreadableError(label, relativePath);
|
|
1379
1972
|
}
|
|
1380
1973
|
return outcome.kind === 'text' ? outcome.text : null;
|
|
@@ -1394,20 +1987,20 @@ export class AccessControlService {
|
|
|
1394
1987
|
// fires only on a file that was read and would not parse.
|
|
1395
1988
|
const activeGroups = await loadActiveGroups(read);
|
|
1396
1989
|
if (!activeGroups.health.ok) {
|
|
1397
|
-
|
|
1990
|
+
logger(`access@${tag}`).error(`groups source ${activeGroups.health.file} is broken (${activeGroups.health.reason}) — groups contribute nothing until it is fixed`);
|
|
1398
1991
|
}
|
|
1399
1992
|
for (const w of activeGroups.warnings)
|
|
1400
|
-
|
|
1993
|
+
logger(`access@${tag}`).warn(w);
|
|
1401
1994
|
const mergeWarnings = mergeGroupsIntoRoles(rolesParsed.index, activeGroups.groups, activeGroups.sourceFile);
|
|
1402
1995
|
for (const w of mergeWarnings)
|
|
1403
|
-
|
|
1996
|
+
logger(`access@${tag}`).warn(w);
|
|
1404
1997
|
const accessFiles = new Map();
|
|
1405
1998
|
const listed = await this.listAccessFilesAtRef(repoDir, commit);
|
|
1406
1999
|
if (listed === 'error') {
|
|
1407
|
-
|
|
2000
|
+
logger(`access@${tag}`).warn('listing access.md files failed; refusing to decide from a partial tree');
|
|
1408
2001
|
throw new AccessUnreadableError(label, 'access.md (ls-tree)');
|
|
1409
2002
|
}
|
|
1410
|
-
for (const p of listed) {
|
|
2003
|
+
for (const p of listed.accessFiles) {
|
|
1411
2004
|
const text = await read(p);
|
|
1412
2005
|
if (text === null)
|
|
1413
2006
|
continue;
|
|
@@ -1419,6 +2012,16 @@ export class AccessControlService {
|
|
|
1419
2012
|
// duplicates, but be defensive.
|
|
1420
2013
|
accessFiles.set(parsed.file.dir, parsed.file);
|
|
1421
2014
|
}
|
|
2015
|
+
// Mirror `loadModel`: plugin principals from the plugins the ref carries,
|
|
2016
|
+
// each under the identity its manifest AT THAT REF declares, so a PR-time
|
|
2017
|
+
// verdict on a skill granted to `plugin/<name>/write` counts the plugin's
|
|
2018
|
+
// writers exactly as the working tree would.
|
|
2019
|
+
const pluginIdentities = new Map();
|
|
2020
|
+
for (const dir of listed.pluginDirs) {
|
|
2021
|
+
const manifest = parseManifestText(await read(`${dir}/${PLUGIN_MANIFEST_FILE}`));
|
|
2022
|
+
pluginIdentities.set(dir, pluginIdentityOf(manifest, path.posix.basename(dir)));
|
|
2023
|
+
}
|
|
2024
|
+
synthesizePluginPrincipals(rolesParsed.index, accessFiles, pluginIdentities);
|
|
1422
2025
|
// Mirror `loadModel`: drop entries whose role ref is unknown to roles.yaml,
|
|
1423
2026
|
// while preserving the built-in `everyone` role.
|
|
1424
2027
|
for (const [, file] of accessFiles) {
|