@bevel-software/platform-core-backend 0.33.0 → 0.34.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/agent-guide/access-control.md +19 -8
- package/agent-guide/html-views.md +231 -5
- package/agent-guide/tool-manuals.md +1 -1
- package/dist/core/catalog-revision.d.ts +12 -1
- package/dist/core/catalog-revision.d.ts.map +1 -1
- package/dist/core/catalog-revision.js +23 -3
- package/dist/core/catalog-revision.js.map +1 -1
- package/dist/core/core-ports.d.ts +41 -0
- package/dist/core/core-ports.d.ts.map +1 -1
- package/dist/core/core-ports.js.map +1 -1
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +14 -7
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +16 -5
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +37 -12
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +16 -0
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +12 -2
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +17 -9
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +12 -2
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +164 -75
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access-mutation.service.d.ts +22 -24
- package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
- package/dist/modules/access/access-mutation.service.js +48 -51
- package/dist/modules/access/access-mutation.service.js.map +1 -1
- package/dist/modules/access/access-view.d.ts.map +1 -1
- package/dist/modules/access/access-view.js +12 -11
- package/dist/modules/access/access-view.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts +3 -0
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +35 -29
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/groups-admin.routes.d.ts.map +1 -1
- package/dist/modules/access/groups-admin.routes.js +4 -2
- package/dist/modules/access/groups-admin.routes.js.map +1 -1
- package/dist/modules/access/groups-admin.service.d.ts +44 -13
- package/dist/modules/access/groups-admin.service.d.ts.map +1 -1
- package/dist/modules/access/groups-admin.service.js +80 -38
- package/dist/modules/access/groups-admin.service.js.map +1 -1
- package/dist/modules/access/groups-edit.d.ts +1 -1
- package/dist/modules/access/groups-edit.d.ts.map +1 -1
- package/dist/modules/access/groups-edit.js +1 -1
- package/dist/modules/access/groups-edit.js.map +1 -1
- package/dist/modules/access/reference-scan.d.ts +116 -24
- package/dist/modules/access/reference-scan.d.ts.map +1 -1
- package/dist/modules/access/reference-scan.js +305 -44
- package/dist/modules/access/reference-scan.js.map +1 -1
- package/dist/modules/access/roles-admin.service.d.ts +17 -8
- package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
- package/dist/modules/access/roles-admin.service.js +32 -23
- package/dist/modules/access/roles-admin.service.js.map +1 -1
- package/dist/modules/access-model/access-grammar.d.ts +35 -10
- package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
- package/dist/modules/access-model/access-grammar.js +60 -20
- package/dist/modules/access-model/access-grammar.js.map +1 -1
- package/dist/modules/access-model/access-splice.d.ts +22 -18
- package/dist/modules/access-model/access-splice.d.ts.map +1 -1
- package/dist/modules/access-model/access-splice.js +20 -14
- package/dist/modules/access-model/access-splice.js.map +1 -1
- package/dist/modules/access-model/group-files.d.ts +1 -0
- package/dist/modules/access-model/group-files.d.ts.map +1 -1
- package/dist/modules/access-model/group-files.js +5 -1
- package/dist/modules/access-model/group-files.js.map +1 -1
- package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.tools.js +13 -3
- package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -1
- package/dist/modules/agent-instructions/compose.d.ts +3 -1
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
- package/dist/modules/agent-instructions/compose.js +3 -1
- package/dist/modules/agent-instructions/compose.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts +5 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.js +16 -1
- package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
- package/dist/modules/agent-scope/agent-scope.contract.d.ts +67 -0
- package/dist/modules/agent-scope/agent-scope.contract.d.ts.map +1 -0
- package/dist/modules/agent-scope/agent-scope.contract.js +27 -0
- package/dist/modules/agent-scope/agent-scope.contract.js.map +1 -0
- package/dist/modules/agent-scope/connection-scope.d.ts +111 -0
- package/dist/modules/agent-scope/connection-scope.d.ts.map +1 -0
- package/dist/modules/agent-scope/connection-scope.js +165 -0
- package/dist/modules/agent-scope/connection-scope.js.map +1 -0
- package/dist/modules/agent-scope/index.d.ts +3 -0
- package/dist/modules/agent-scope/index.d.ts.map +1 -0
- package/dist/modules/agent-scope/index.js +3 -0
- package/dist/modules/agent-scope/index.js.map +1 -0
- package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
- package/dist/modules/audit/agent-audit.service.js +2 -0
- package/dist/modules/audit/agent-audit.service.js.map +1 -1
- package/dist/modules/audit/audit.contract.d.ts +6 -0
- package/dist/modules/audit/audit.contract.d.ts.map +1 -1
- package/dist/modules/audit/audit.contract.js.map +1 -1
- package/dist/modules/auth/account-admission.d.ts +12 -0
- package/dist/modules/auth/account-admission.d.ts.map +1 -1
- package/dist/modules/auth/account-admission.js +15 -0
- package/dist/modules/auth/account-admission.js.map +1 -1
- package/dist/modules/auth/auth.middleware.d.ts +27 -1
- package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
- package/dist/modules/auth/auth.middleware.js +42 -4
- package/dist/modules/auth/auth.middleware.js.map +1 -1
- package/dist/modules/auth/auth.routes.d.ts +13 -9
- package/dist/modules/auth/auth.routes.d.ts.map +1 -1
- package/dist/modules/auth/auth.routes.js +35 -29
- package/dist/modules/auth/auth.routes.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +55 -6
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +128 -34
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/auth/index.d.ts +1 -1
- package/dist/modules/auth/index.d.ts.map +1 -1
- package/dist/modules/auth/index.js +1 -1
- package/dist/modules/auth/index.js.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.js +2 -9
- package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +68 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +33 -0
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.d.ts +8 -1
- package/dist/modules/declared-variables/declared-variables.routes.d.ts.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.js +13 -2
- package/dist/modules/declared-variables/declared-variables.routes.js.map +1 -1
- package/dist/modules/embed/embed.tools.d.ts.map +1 -1
- package/dist/modules/embed/embed.tools.js +5 -0
- package/dist/modules/embed/embed.tools.js.map +1 -1
- package/dist/modules/kb-fs/index.d.ts +2 -0
- package/dist/modules/kb-fs/index.d.ts.map +1 -1
- package/dist/modules/kb-fs/index.js +1 -0
- package/dist/modules/kb-fs/index.js.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.js +7 -1
- package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
- package/dist/modules/kb-fs/repo-path.d.ts +26 -6
- package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
- package/dist/modules/kb-fs/repo-path.js +22 -7
- package/dist/modules/kb-fs/repo-path.js.map +1 -1
- package/dist/modules/kb-fs/tmp-ref.d.ts +36 -0
- package/dist/modules/kb-fs/tmp-ref.d.ts.map +1 -0
- package/dist/modules/kb-fs/tmp-ref.js +76 -0
- package/dist/modules/kb-fs/tmp-ref.js.map +1 -0
- package/dist/modules/mcp/mcp-auth.middleware.d.ts +7 -0
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +14 -1
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/mcp.routes.d.ts +7 -1
- package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.routes.js +39 -4
- package/dist/modules/mcp/mcp.routes.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +11 -0
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +68 -13
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +45 -10
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts +9 -0
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js +34 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
- package/dist/modules/plugins/index.d.ts +1 -1
- package/dist/modules/plugins/index.d.ts.map +1 -1
- package/dist/modules/plugins/index.js.map +1 -1
- package/dist/modules/plugins/plugin-links.d.ts +18 -2
- package/dist/modules/plugins/plugin-links.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-links.js.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +5 -1
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +32 -5
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +9 -0
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.js +15 -3
- package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
- package/dist/modules/skills/index.d.ts +3 -3
- package/dist/modules/skills/index.d.ts.map +1 -1
- package/dist/modules/skills/index.js +0 -2
- package/dist/modules/skills/index.js.map +1 -1
- package/dist/modules/skills/skills.contract.d.ts +16 -0
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +6 -1
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +24 -12
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.d.ts +11 -0
- package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -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 +1 -0
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +4 -1
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/index.d.ts +1 -1
- package/dist/modules/tool-auth/index.d.ts.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.d.ts +9 -0
- package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.js +1 -0
- package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts +27 -3
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +32 -6
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/tool-context.d.ts +7 -0
- package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-context.js +3 -0
- package/dist/modules/tool-helpers/tool-context.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts +15 -0
- package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.js +19 -0
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts +10 -0
- package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
- package/dist/modules/tool-helpers/validate-token.d.ts +3 -1
- package/dist/modules/tool-helpers/validate-token.d.ts.map +1 -1
- package/dist/modules/tool-helpers/validate-token.js +1 -1
- package/dist/modules/tool-helpers/validate-token.js.map +1 -1
- package/dist/modules/tool-manuals/index.d.ts +3 -3
- package/dist/modules/tool-manuals/index.d.ts.map +1 -1
- package/dist/modules/tool-manuals/index.js +0 -2
- package/dist/modules/tool-manuals/index.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +19 -6
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +7 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.js +41 -4
- package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +13 -6
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +23 -15
- 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 +25 -10
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/tool-registry/manual.routes.d.ts +8 -1
- package/dist/modules/tool-registry/manual.routes.d.ts.map +1 -1
- package/dist/modules/tool-registry/manual.routes.js +27 -7
- package/dist/modules/tool-registry/manual.routes.js.map +1 -1
- package/dist/modules/tool-registry/tool.contract.d.ts +7 -0
- package/dist/modules/tool-registry/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-registry/tool.contract.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 +14 -9
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +2 -2
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +20 -35
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +2 -1
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +3 -0
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts +28 -4
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.js +61 -40
- package/dist/modules/workspace/file-readers/doc-extract.service.js.map +1 -1
- package/dist/modules/workspace/index.d.ts +8 -0
- package/dist/modules/workspace/index.d.ts.map +1 -1
- package/dist/modules/workspace/index.js +8 -0
- package/dist/modules/workspace/index.js.map +1 -1
- package/dist/modules/workspace/nul-scan.d.ts +19 -0
- package/dist/modules/workspace/nul-scan.d.ts.map +1 -0
- package/dist/modules/workspace/nul-scan.js +55 -0
- package/dist/modules/workspace/nul-scan.js.map +1 -0
- package/dist/modules/workspace/pattern-matcher.contract.d.ts +110 -27
- package/dist/modules/workspace/pattern-matcher.contract.d.ts.map +1 -1
- package/dist/modules/workspace/pattern-matcher.contract.js +30 -13
- package/dist/modules/workspace/pattern-matcher.contract.js.map +1 -1
- package/dist/modules/workspace/ripgrep-search.d.ts +54 -0
- package/dist/modules/workspace/ripgrep-search.d.ts.map +1 -0
- package/dist/modules/workspace/ripgrep-search.js +657 -0
- package/dist/modules/workspace/ripgrep-search.js.map +1 -0
- package/dist/modules/workspace/ripgrep-version.d.ts +15 -0
- package/dist/modules/workspace/ripgrep-version.d.ts.map +1 -0
- package/dist/modules/workspace/ripgrep-version.js +31 -0
- package/dist/modules/workspace/ripgrep-version.js.map +1 -0
- package/dist/modules/workspace/startup/beside-checkout.d.ts +8 -5
- package/dist/modules/workspace/startup/beside-checkout.d.ts.map +1 -1
- package/dist/modules/workspace/startup/beside-checkout.js +14 -5
- package/dist/modules/workspace/startup/beside-checkout.js.map +1 -1
- package/dist/modules/workspace/worker-pool.d.ts +3 -4
- package/dist/modules/workspace/worker-pool.d.ts.map +1 -1
- package/dist/modules/workspace/worker-pool.js.map +1 -1
- package/dist/modules/workspace/workspace-tmp.d.ts +146 -0
- package/dist/modules/workspace/workspace-tmp.d.ts.map +1 -0
- package/dist/modules/workspace/workspace-tmp.js +435 -0
- package/dist/modules/workspace/workspace-tmp.js.map +1 -0
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +5 -26
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +47 -6
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +86 -6
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +460 -116
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/shared/path-order.d.ts +28 -8
- package/dist/shared/path-order.d.ts.map +1 -1
- package/dist/shared/path-order.js +37 -8
- package/dist/shared/path-order.js.map +1 -1
- package/migrations/0022_session_version.sql +6 -0
- package/migrations/0023_agent_path.sql +3 -0
- package/migrations/meta/0022_snapshot.json +2360 -0
- package/migrations/meta/0023_snapshot.json +2378 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +3 -3
- package/src/__tests__/pg-scratch.test.ts +69 -0
- package/src/__tests__/pg-scratch.ts +31 -0
- package/src/__tests__/slow-ripgrep.ts +65 -0
- package/src/core/__tests__/boot-without-ripgrep.test.ts +34 -0
- package/src/core/__tests__/catalog-revision.test.ts +89 -0
- package/src/core/__tests__/damaged-document.tools.test.ts +2 -2
- package/src/core/__tests__/gated-boot.test.ts +8 -1
- package/src/core/__tests__/lifecycle.test.ts +46 -13
- package/src/core/catalog-revision.ts +24 -2
- package/src/core/core-ports.ts +42 -0
- package/src/core/create-core-server.ts +13 -0
- package/src/core/create-core-services.ts +49 -12
- package/src/core/lifecycle.ts +26 -2
- package/src/index.ts +1 -1
- package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +12 -0
- package/src/modules/access/__tests__/access-control.colliding-name-cases.test.ts +113 -0
- package/src/modules/access/__tests__/access-control.frontmatter-head.test.ts +117 -0
- package/src/modules/access/__tests__/access-control.group-named-admin.test.ts +122 -0
- package/src/modules/access/__tests__/access-control.preview-relocation.test.ts +50 -0
- package/src/modules/access/__tests__/access-declarations.test.ts +19 -0
- package/src/modules/access/__tests__/access-groups.test.ts +55 -22
- package/src/modules/access/__tests__/access-mutation.service.test.ts +85 -59
- package/src/modules/access/__tests__/access-restrict-here.test.ts +7 -5
- package/src/modules/access/__tests__/access.routes.admins.test.ts +1 -0
- package/src/modules/access/__tests__/access.routes.batch.test.ts +2 -0
- package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +2 -0
- package/src/modules/access/__tests__/access.routes.group-grant.test.ts +50 -14
- package/src/modules/access/__tests__/access.routes.overrides.test.ts +2 -1
- package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +1 -0
- package/src/modules/access/__tests__/access.routes.prospective.test.ts +2 -0
- package/src/modules/access/__tests__/access.routes.revoke.test.ts +18 -13
- package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +2 -0
- package/src/modules/access/__tests__/grant-sources.test.ts +20 -15
- package/src/modules/access/__tests__/groups-admin.routes.test.ts +61 -0
- package/src/modules/access/__tests__/groups-admin.service.test.ts +211 -18
- package/src/modules/access/__tests__/reference-scan.test.ts +366 -14
- package/src/modules/access/__tests__/roles-admin.security.test.ts +1 -1
- package/src/modules/access/__tests__/roles-admin.service.test.ts +43 -22
- package/src/modules/access/__tests__/roles-capabilities.test.ts +6 -2
- package/src/modules/access/__tests__/roles.routes.test.ts +1 -1
- package/src/modules/access/access-control.interface.ts +17 -10
- package/src/modules/access/access-control.service.ts +158 -72
- package/src/modules/access/access-mutation.service.ts +48 -53
- package/src/modules/access/access-view.ts +12 -10
- package/src/modules/access/access.routes.ts +43 -33
- package/src/modules/access/groups-admin.routes.ts +4 -2
- package/src/modules/access/groups-admin.service.ts +114 -44
- package/src/modules/access/groups-edit.ts +1 -1
- package/src/modules/access/reference-scan.ts +319 -46
- package/src/modules/access/roles-admin.service.ts +43 -26
- package/src/modules/access/synced-groups-writer.ts +1 -1
- package/src/modules/access-model/__tests__/access-grammar.test.ts +38 -1
- package/src/modules/access-model/__tests__/access-splice.test.ts +50 -8
- package/src/modules/access-model/access-grammar.ts +65 -20
- package/src/modules/access-model/access-splice.ts +31 -23
- package/src/modules/access-model/group-files.ts +5 -0
- package/src/modules/agent-guide/__tests__/agent-guide.test.ts +31 -0
- package/src/modules/agent-guide/agent-guide.tools.ts +13 -2
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +1 -0
- package/src/modules/agent-instructions/compose.ts +3 -1
- package/src/modules/agent-instructions/shared-file-rules.ts +17 -1
- package/src/modules/agent-scope/__tests__/agent-surfaces.connect-as.test.ts +421 -0
- package/src/modules/agent-scope/__tests__/connection-scope.test.ts +46 -0
- package/src/modules/agent-scope/__tests__/in-memory-kb.ts +148 -0
- package/src/modules/agent-scope/agent-scope.contract.ts +83 -0
- package/src/modules/agent-scope/connection-scope.ts +199 -0
- package/src/modules/agent-scope/index.ts +15 -0
- package/src/modules/audit/agent-audit.service.ts +2 -0
- package/src/modules/audit/audit.contract.ts +6 -0
- package/src/modules/auth/__tests__/auth.middleware.test.ts +30 -2
- package/src/modules/auth/__tests__/auth.routes.test.ts +71 -5
- package/src/modules/auth/__tests__/auth.service.test.ts +90 -9
- package/src/modules/auth/__tests__/password-change-sessions.pg.test.ts +269 -0
- package/src/modules/auth/account-admission.ts +19 -0
- package/src/modules/auth/auth.middleware.ts +45 -4
- package/src/modules/auth/auth.routes.ts +46 -29
- package/src/modules/auth/auth.service.ts +135 -34
- package/src/modules/auth/index.ts +2 -0
- package/src/modules/auth/oidc-auth-provider.ts +2 -9
- package/src/modules/database/__tests__/session-version-migration.pg.test.ts +133 -0
- package/src/modules/database/core-schema.ts +33 -0
- package/src/modules/declared-variables/__tests__/declared-variables.route.test.ts +44 -1
- package/src/modules/declared-variables/declared-variables.routes.ts +12 -1
- package/src/modules/embed/__tests__/embed.tools.test.ts +12 -0
- package/src/modules/embed/embed.tools.ts +5 -0
- package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +25 -0
- package/src/modules/kb-fs/__tests__/repo-path.test.ts +53 -8
- package/src/modules/kb-fs/index.ts +2 -0
- package/src/modules/kb-fs/locking-filesystem.ts +7 -1
- package/src/modules/kb-fs/repo-path.ts +33 -7
- package/src/modules/kb-fs/tmp-ref.ts +81 -0
- package/src/modules/mcp/__tests__/connect-as.pg.test.ts +137 -0
- package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +29 -5
- package/src/modules/mcp/__tests__/mcp-routes-harness.ts +8 -0
- package/src/modules/mcp/__tests__/mcp.connect-as.test.ts +195 -0
- package/src/modules/mcp/__tests__/mcp.routes.connect-as.test.ts +192 -0
- package/src/modules/mcp/__tests__/oauth-consent.connect-as.test.ts +146 -0
- package/src/modules/mcp/mcp-auth.middleware.ts +19 -2
- package/src/modules/mcp/mcp.routes.ts +39 -3
- package/src/modules/mcp/mcp.service.ts +107 -17
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +46 -8
- package/src/modules/mcp/oauth/oauth-consent.routes.ts +41 -1
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +38 -2
- package/src/modules/plugins/index.ts +1 -1
- package/src/modules/plugins/plugin-links.ts +19 -2
- package/src/modules/plugins/plugins.routes.ts +35 -4
- package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +113 -0
- package/src/modules/secrets-vault/secrets-vault.routes.ts +23 -3
- package/src/modules/skills/__tests__/skills.service.test.ts +34 -0
- package/src/modules/skills/index.ts +7 -2
- package/src/modules/skills/skills.contract.ts +17 -0
- package/src/modules/skills/skills.service.ts +5 -1
- package/src/modules/skills/skills.tools.ts +44 -13
- package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +4 -0
- package/src/modules/tool-auth/external-api-key.interface.ts +11 -1
- package/src/modules/tool-auth/external-api-key.service.ts +5 -2
- package/src/modules/tool-auth/index.ts +1 -1
- package/src/modules/tool-auth/internal-token.service.ts +10 -0
- package/src/modules/tool-auth/tool-auth.middleware.ts +45 -3
- package/src/modules/tool-helpers/__tests__/can-write.tools.test.ts +3 -1
- package/src/modules/tool-helpers/tool-context.ts +10 -0
- package/src/modules/tool-helpers/tool-handler.ts +34 -0
- package/src/modules/tool-helpers/tool.contract.ts +10 -0
- package/src/modules/tool-helpers/validate-token.ts +4 -2
- package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +80 -4
- package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +42 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +3 -3
- package/src/modules/tool-manuals/index.ts +6 -2
- package/src/modules/tool-manuals/tool-manuals.contract.ts +18 -6
- package/src/modules/tool-manuals/tool-manuals.routes.ts +40 -3
- package/src/modules/tool-manuals/tool-manuals.service.ts +31 -15
- package/src/modules/tool-manuals/tool-manuals.tools.ts +28 -11
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +2 -2
- package/src/modules/tool-registry/manual.routes.ts +26 -5
- package/src/modules/tool-registry/tool.contract.ts +7 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +1 -1
- package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +1 -1
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +54 -3
- package/src/modules/workflow/agent-tools/workflow.tools.ts +14 -9
- package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +130 -3
- package/src/modules/workflow/git/git.service.ts +29 -48
- package/src/modules/workflow/workflow.service.ts +5 -0
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +2 -2
- package/src/modules/workspace/__tests__/holds-nul.test.ts +66 -0
- package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +3 -3
- package/src/modules/workspace/__tests__/ripgrep-search.test.ts +570 -0
- package/src/modules/workspace/__tests__/ripgrep-version.test.ts +29 -0
- package/src/modules/workspace/__tests__/workspace-tmp.tools.test.ts +928 -0
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +17 -37
- package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +20 -8
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +2 -2
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +22 -10
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +393 -113
- package/src/modules/workspace/file-readers/__tests__/doc-extract.service.test.ts +10 -0
- package/src/modules/workspace/file-readers/doc-extract.service.ts +71 -38
- package/src/modules/workspace/index.ts +20 -0
- package/src/modules/workspace/nul-scan.ts +50 -0
- package/src/modules/workspace/pattern-matcher.contract.ts +123 -32
- package/src/modules/workspace/ripgrep-search.ts +715 -0
- package/src/modules/workspace/ripgrep-version.ts +35 -0
- package/src/modules/workspace/startup/__tests__/beside-checkout.test.ts +11 -0
- package/src/modules/workspace/startup/beside-checkout.ts +13 -5
- package/src/modules/workspace/worker-pool.ts +3 -4
- package/src/modules/workspace/workspace-tmp.ts +457 -0
- package/src/modules/workspace/workspace.routes.ts +6 -27
- package/src/modules/workspace/workspace.service.ts +92 -15
- package/src/modules/workspace/workspace.tools.ts +500 -155
- package/src/shared/path-order.ts +37 -8
- package/starter-packs/engineering/Plugins/engineering-starter/access.md +1 -1
- package/starter-packs/general/Plugins/general-starter/access.md +1 -1
- package/starter-packs/operations/Plugins/operations-starter/access.md +1 -1
- package/starter-packs/sales/Plugins/sales-starter/access.md +1 -1
- package/starter-packs/support/Plugins/support-starter/access.md +1 -1
- package/dist/modules/workspace/bounded-matcher.d.ts +0 -73
- package/dist/modules/workspace/bounded-matcher.d.ts.map +0 -1
- package/dist/modules/workspace/bounded-matcher.js +0 -200
- package/dist/modules/workspace/bounded-matcher.js.map +0 -1
- package/src/modules/workspace/__tests__/bounded-matcher.test.ts +0 -213
- package/src/modules/workspace/bounded-matcher.ts +0 -271
|
@@ -40,11 +40,10 @@ import type { IAccessControl } from './access-control.interface.js';
|
|
|
40
40
|
import { isAbsence } from '../../shared/fs.contract.js';
|
|
41
41
|
import { isTextBytes } from '../workspace/index.js';
|
|
42
42
|
import {
|
|
43
|
-
type Verb,
|
|
44
43
|
ADMIN_CANONICAL,
|
|
45
44
|
KNOWN_VERBS,
|
|
46
|
-
ROLE_TOKEN_PREFIX,
|
|
47
45
|
canonicalRoleName,
|
|
46
|
+
splitPrincipalToken,
|
|
48
47
|
accessFrontmatterExtensionList,
|
|
49
48
|
} from '../access-model/index.js';
|
|
50
49
|
import {
|
|
@@ -52,9 +51,8 @@ import {
|
|
|
52
51
|
spliceGrant,
|
|
53
52
|
validatePrincipal,
|
|
54
53
|
AccessSpliceError,
|
|
55
|
-
type Principal,
|
|
56
|
-
type TokenMatch,
|
|
57
54
|
} from '../access-model/index.js';
|
|
55
|
+
import type { Verb, Principal, TokenMatch } from '../access-model/index.js';
|
|
58
56
|
import { WorkflowDomainError } from '../../shared/domain-errors.js';
|
|
59
57
|
import {
|
|
60
58
|
canCarryFrontmatter,
|
|
@@ -90,23 +88,21 @@ export const ADMIN_ROOT_WRITE_MESSAGE =
|
|
|
90
88
|
* this target touches the Admin ROLE's write at the repository root — the
|
|
91
89
|
* root folder's `access.md`, or a file directly in the root. The resolver
|
|
92
90
|
* ignores such a rule anyway (the Admin write floor), so writing it would only
|
|
93
|
-
* leave a line that says something the app does not do. A bare
|
|
94
|
-
*
|
|
95
|
-
*
|
|
91
|
+
* leave a line that says something the app does not do. A bare `Admin` is the
|
|
92
|
+
* role (a bare name is always the role when one exists, and roles.yaml always
|
|
93
|
+
* declares Admin); `group/Admin` is a group and never the role. Targets arrive
|
|
94
|
+
* in one spelling: the routes refuse `.` and `..` segments.
|
|
96
95
|
*/
|
|
97
96
|
export function removesAdminRootWrite(
|
|
98
97
|
kind: TargetKind,
|
|
99
98
|
repoRelTarget: string,
|
|
100
99
|
principal: Principal,
|
|
101
100
|
verb: Verb | undefined,
|
|
102
|
-
tokenMatch: TokenMatch,
|
|
103
101
|
): boolean {
|
|
104
102
|
if (principal.kind !== 'role') return false;
|
|
105
103
|
if (!touchesRootWrite(kind, repoRelTarget, verb)) return false;
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
if (!explicit && tokenMatch === 'exact') return false;
|
|
109
|
-
return (explicit ? canonical.slice(ROLE_TOKEN_PREFIX.length) : canonical) === ADMIN_CANONICAL;
|
|
104
|
+
const { prefix, name } = splitPrincipalToken(canonicalRoleName(principal.role));
|
|
105
|
+
return prefix !== 'group' && name === ADMIN_CANONICAL;
|
|
110
106
|
}
|
|
111
107
|
|
|
112
108
|
/** Whether a mutation on `verb` (absent = every verb) at this target reaches write at the repository root. */
|
|
@@ -276,38 +272,40 @@ export class AccessMutationService {
|
|
|
276
272
|
}
|
|
277
273
|
|
|
278
274
|
/**
|
|
279
|
-
* How a REVOKE of `principal` matches file tokens
|
|
275
|
+
* How a REVOKE of `principal` matches file tokens. `'name'` strips the
|
|
276
|
+
* principal's explicit spelling AND the bare name (never the other kind's
|
|
277
|
+
* explicit spelling — see `TokenMatch`); `'exact'` strips the literal
|
|
278
|
+
* spelling alone. The bare name is stripped only when it is THIS
|
|
279
|
+
* principal's under role-first precedence:
|
|
280
280
|
*
|
|
281
|
-
* -
|
|
282
|
-
*
|
|
283
|
-
* group's,
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
281
|
+
* - A ROLE (`role/<name>`, or a bare name) owns the bare key unless no
|
|
282
|
+
* role of that name exists and a group does — then the bare token is
|
|
283
|
+
* the group's, and matching is `'exact'`. A model that fails to load
|
|
284
|
+
* keeps it `'exact'` too.
|
|
285
|
+
* - A GROUP (`group/<name>`) owns the bare key only while no role of
|
|
286
|
+
* that name exists — otherwise the bare token is the role's, and
|
|
287
|
+
* matching is `'exact'`. A group that has VANISHED from the active
|
|
288
|
+
* source still reads correctly: the question is whether a ROLE holds
|
|
289
|
+
* the name. A model that fails to load says nothing about roles, so a
|
|
290
|
+
* group revoke then stays `'exact'` rather than risk a role's grant.
|
|
289
291
|
*
|
|
290
292
|
* Judged against THIS workspace's merged principal index (`kbPrincipals` —
|
|
291
293
|
* the same model the resolver reads), so revoke agrees with resolution on
|
|
292
|
-
* who owns the bare key.
|
|
293
|
-
* unshadowed — matching degrades to the pre-groups name-level behavior.
|
|
294
|
-
*
|
|
295
|
-
* GROUP revokes never take the name-level path: the route passes
|
|
296
|
-
* `tokenMatch: 'exact'` explicitly (see `revoke`'s `opts`), because this
|
|
297
|
-
* shadow probe cannot tell a group apart from a role once the group has
|
|
298
|
-
* VANISHED from the active source — the bare name then reads "unshadowed"
|
|
299
|
-
* and a name-level group revoke would strip a same-named role's live
|
|
300
|
-
* `role/<Name>` grant.
|
|
294
|
+
* who owns the bare key.
|
|
301
295
|
*/
|
|
302
296
|
private async revokeTokenMatch(workspaceId: string, principal: Principal): Promise<TokenMatch> {
|
|
303
297
|
if (principal.kind !== 'role') return 'exact'; // user matching ignores the mode
|
|
304
|
-
const
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
298
|
+
const { prefix, name } = splitPrincipalToken(canonicalRoleName(principal.role));
|
|
299
|
+
const { roles, groups } = await this.accessControl.kbPrincipals(workspaceId);
|
|
300
|
+
const isRole = roles.some((r) => canonicalRoleName(r) === name);
|
|
301
|
+
if (prefix === 'group') {
|
|
302
|
+
// `roles` always carries at least `Everyone` once the model loads.
|
|
303
|
+
return isRole || roles.length === 0 ? 'exact' : 'name';
|
|
304
|
+
}
|
|
305
|
+
const isGroup = groups.some((g) => canonicalRoleName(g) === name);
|
|
306
|
+
// No roles at all means the model failed to load: stay exact, so a role
|
|
307
|
+
// revoke never strips a same-named group's bare grant.
|
|
308
|
+
return roles.length === 0 || (!isRole && isGroup) ? 'exact' : 'name';
|
|
311
309
|
}
|
|
312
310
|
|
|
313
311
|
/**
|
|
@@ -372,11 +370,10 @@ export class AccessMutationService {
|
|
|
372
370
|
// When present, strip ONLY this verb (a per-checkbox toggle in the share UI);
|
|
373
371
|
// absent strips the principal from every verb (the whole-principal Remove).
|
|
374
372
|
verb?: Verb,
|
|
375
|
-
// `tokenMatch` overrides the
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
// `role/<Name>` grant.
|
|
373
|
+
// `tokenMatch` overrides the derived matching (see revokeTokenMatch).
|
|
374
|
+
// One production caller: the plugin-links unlink pins `'exact'` for its
|
|
375
|
+
// `plugin/<Name>/<verb>` tokens. Pin only `'exact'` — a pinned `'name'`
|
|
376
|
+
// would bypass the guard that never strips across kinds.
|
|
380
377
|
opts?: { tokenMatch?: TokenMatch },
|
|
381
378
|
): Promise<{ changed: boolean; editPath: string }> {
|
|
382
379
|
void _actingUserEmail; // referenced to satisfy no-unused-vars until something consumes it
|
|
@@ -386,8 +383,8 @@ export class AccessMutationService {
|
|
|
386
383
|
// missing FILE node is a bad target and should surface, not silently no-op
|
|
387
384
|
// — same rule grant() uses.
|
|
388
385
|
const original = await this.readOrEmpty(workspaceId, kind, repoRelTarget, editPath);
|
|
389
|
-
// Alias-tolerant vs exact-token matching, decided by
|
|
390
|
-
// see revokeTokenMatch — unless the caller pinned it.
|
|
386
|
+
// Alias-tolerant vs exact-token matching, decided by who owns the bare
|
|
387
|
+
// name — see revokeTokenMatch — unless the caller pinned it.
|
|
391
388
|
const tokenMatch = opts?.tokenMatch ?? (await this.revokeTokenMatch(workspaceId, principal));
|
|
392
389
|
let next = original;
|
|
393
390
|
let changed = false;
|
|
@@ -409,7 +406,7 @@ export class AccessMutationService {
|
|
|
409
406
|
// Refused only when an Admin write entry would really go: a whole-row
|
|
410
407
|
// Remove at a root that grants Admin other verbs but no write line (write
|
|
411
408
|
// comes from the floor) strips those verbs as usual.
|
|
412
|
-
if (writeRemoved && removesAdminRootWrite(kind, repoRelTarget, principal, verb
|
|
409
|
+
if (writeRemoved && removesAdminRootWrite(kind, repoRelTarget, principal, verb)) {
|
|
413
410
|
throw adminRootWriteRefusal();
|
|
414
411
|
}
|
|
415
412
|
if (!changed) return { changed: false, editPath };
|
|
@@ -457,7 +454,7 @@ export class AccessMutationService {
|
|
|
457
454
|
repoRelTarget: string,
|
|
458
455
|
principal: Principal,
|
|
459
456
|
verb?: Verb,
|
|
460
|
-
// Same override as `revoke
|
|
457
|
+
// Same override as `revoke`; no production caller passes it here.
|
|
461
458
|
opts?: { tokenMatch?: TokenMatch },
|
|
462
459
|
): Promise<{ changed: boolean; editPath: string }> {
|
|
463
460
|
this.assertPrincipalSafe(principal);
|
|
@@ -465,10 +462,10 @@ export class AccessMutationService {
|
|
|
465
462
|
const original = await this.readOrEmpty(workspaceId, kind, repoRelTarget, editPath);
|
|
466
463
|
|
|
467
464
|
const verbsToDeny = verb ? [verb] : KNOWN_VERBS;
|
|
468
|
-
// Same
|
|
469
|
-
//
|
|
465
|
+
// Same matching as revoke(): the strip must not swallow a same-named
|
|
466
|
+
// OTHER principal's grant (`group/<name>` vs the role's bare/`role/`).
|
|
470
467
|
const tokenMatch = opts?.tokenMatch ?? (await this.revokeTokenMatch(workspaceId, principal));
|
|
471
|
-
if (removesAdminRootWrite(kind, repoRelTarget, principal, verb
|
|
468
|
+
if (removesAdminRootWrite(kind, repoRelTarget, principal, verb)) {
|
|
472
469
|
throw adminRootWriteRefusal();
|
|
473
470
|
}
|
|
474
471
|
// A PERSON who is an admin keeps root write through the floor, so a deny
|
|
@@ -508,10 +505,8 @@ export class AccessMutationService {
|
|
|
508
505
|
|
|
509
506
|
// (3) Assert the deny actually removed effective access on the targeted
|
|
510
507
|
// verb(s) — else roll back. The check evaluates the SAME token identity
|
|
511
|
-
// the deny above spliced (`tokenMatch` rides along)
|
|
512
|
-
//
|
|
513
|
-
// a same-named role's surviving `role/<Name>` grant as the group still
|
|
514
|
-
// having access — rolling back a fully effective deny as "ineffective".
|
|
508
|
+
// the deny above spliced (`tokenMatch` rides along), so a pinned exact
|
|
509
|
+
// deny is never judged by spellings it did not touch.
|
|
515
510
|
const stillHas = await this.principalStillHasAccess(
|
|
516
511
|
workspaceId,
|
|
517
512
|
kind,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
EVERYONE_CANONICAL,
|
|
14
14
|
KNOWN_VERBS,
|
|
15
15
|
ROLE_TOKEN_PREFIX,
|
|
16
|
+
GROUP_TOKEN_PREFIX,
|
|
16
17
|
type Verb,
|
|
17
18
|
} from '../access-model/index.js';
|
|
18
19
|
|
|
@@ -51,13 +52,12 @@ export async function resolveAccessView(
|
|
|
51
52
|
// files). Keyed `u:<email>` / `r:<role>` / `g:<group>` to match the
|
|
52
53
|
// dialog's row keys, so each row can show where its access comes from and
|
|
53
54
|
// which verbs are removable here. Groups get their OWN `g:` namespace: a
|
|
54
|
-
// group and a role sharing a name are DIFFERENT principals (
|
|
55
|
-
// `role/<name>`), and one shared `r:` entry could only
|
|
56
|
-
// them. Each kind resolves through the token
|
|
57
|
-
// principal — a group through
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
// it has no alias). A row whose verbs resolve only via a
|
|
55
|
+
// group and a role sharing a name are DIFFERENT principals (`group/<name>`
|
|
56
|
+
// vs the bare name and `role/<name>`), and one shared `r:` entry could only
|
|
57
|
+
// describe one of them. Each kind resolves through the explicit token that
|
|
58
|
+
// IS that principal — a group through `group/<name>`, a role through
|
|
59
|
+
// `role/<name>` (the built-in `everyone` keeps its bare spelling, it has no
|
|
60
|
+
// alias); the bare spelling counts toward whichever kind owns it. A row whose verbs resolve only via a
|
|
61
61
|
// group/everyone/rescue has no source (the verb is absent) and renders
|
|
62
62
|
// non-actionable. Built over the union of every principal in the four
|
|
63
63
|
// eligible lists (kinded `principals`, with the name-only `roles` list as
|
|
@@ -96,9 +96,11 @@ export async function resolveAccessView(
|
|
|
96
96
|
await Promise.all([
|
|
97
97
|
...[...collectives.entries()].map(async ([key, p]) => {
|
|
98
98
|
const token =
|
|
99
|
-
p.kind === '
|
|
100
|
-
? `${
|
|
101
|
-
: p.name
|
|
99
|
+
p.kind === 'group'
|
|
100
|
+
? `${GROUP_TOKEN_PREFIX}${p.name}`
|
|
101
|
+
: p.kind === 'role' && canonicalRoleName(p.name) !== EVERYONE_CANONICAL
|
|
102
|
+
? `${ROLE_TOKEN_PREFIX}${p.name}`
|
|
103
|
+
: p.name;
|
|
102
104
|
const principal = { kind: 'role' as const, role: token };
|
|
103
105
|
const [grants, denied] = await Promise.all([
|
|
104
106
|
accessControl.grantSources(workspaceId, kind, repoRelTarget, principal),
|
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
} from './access-control.interface.js';
|
|
20
20
|
import type { WorkspaceService } from '../workspace/index.js';
|
|
21
21
|
import type { KbContext } from '../../shared/kb-context.js';
|
|
22
|
+
import type { ITreeWalker } from '../../shared/fs.contract.js';
|
|
22
23
|
import { branchForWorkspaceId } from '../../shared/workspace-id.js';
|
|
23
24
|
import type { AuthService, IAccountSwitchGuard } from '../auth/index.js';
|
|
24
25
|
import type { WorkflowService, WorkflowEventBus } from '../workflow/index.js';
|
|
@@ -39,7 +40,10 @@ import {
|
|
|
39
40
|
PLUGIN_TOKEN_PREFIX,
|
|
40
41
|
PLUGIN_TOKEN_VERBS,
|
|
41
42
|
ROLE_TOKEN_PREFIX,
|
|
43
|
+
GROUP_TOKEN_PREFIX,
|
|
44
|
+
splitPrincipalToken,
|
|
42
45
|
type PluginTokenVerb,
|
|
46
|
+
type Principal,
|
|
43
47
|
type Verb,
|
|
44
48
|
} from '../access-model/index.js';
|
|
45
49
|
import { listAccessDeclarationsUnder } from './access-declarations.js';
|
|
@@ -47,7 +51,6 @@ import { emailsInView, holderPrincipals, labelAccountHolders, resolveAccessView
|
|
|
47
51
|
import type { AccessView, LabelledAccessView } from './access-view.js';
|
|
48
52
|
import { toHttpError as sharedToHttpError, requireNonEmptyString as sharedRequireNonEmptyString } from './admin-route-helpers.js';
|
|
49
53
|
import { RolesAdminService } from './roles-admin.service.js';
|
|
50
|
-
import type { Principal } from '../access-model/index.js';
|
|
51
54
|
import type { Database } from '../database/index.js';
|
|
52
55
|
import { users } from '../database/index.js';
|
|
53
56
|
import { canonicalEmail } from '../../shared/email-identity.js';
|
|
@@ -91,6 +94,8 @@ export function createAccessRoutes(
|
|
|
91
94
|
eventBus: WorkflowEventBus,
|
|
92
95
|
db: Database,
|
|
93
96
|
kb: KbContext,
|
|
97
|
+
/** The composition root's walk, for the roles admin's reference scan. */
|
|
98
|
+
disk: ITreeWalker,
|
|
94
99
|
/** This deployment's configured admins — the break-glass recovery roster. */
|
|
95
100
|
recoveryAdmins: readonly string[] = [],
|
|
96
101
|
): express.Router {
|
|
@@ -101,6 +106,7 @@ export function createAccessRoutes(
|
|
|
101
106
|
workspaceService,
|
|
102
107
|
workflowService,
|
|
103
108
|
accessControl,
|
|
109
|
+
disk,
|
|
104
110
|
kbDirName,
|
|
105
111
|
() => kb.defaultBranch,
|
|
106
112
|
eventBus,
|
|
@@ -161,10 +167,10 @@ export function createAccessRoutes(
|
|
|
161
167
|
/**
|
|
162
168
|
* What the mutation routes accept as a principal. `group` and `plugin`
|
|
163
169
|
* exist only at this boundary: in the access.md entry grammar a group grant
|
|
164
|
-
* is
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
170
|
+
* is the `group/<Name>` token and a plugin grant is the
|
|
171
|
+
* `plugin/<Name>/<verb>` token, so both are spliced as role-shaped
|
|
172
|
+
* principals — the separate kinds buy validation against the right
|
|
173
|
+
* namespace and honest 404s.
|
|
168
174
|
*/
|
|
169
175
|
type RoutePrincipal =
|
|
170
176
|
| Principal
|
|
@@ -173,14 +179,16 @@ export function createAccessRoutes(
|
|
|
173
179
|
|
|
174
180
|
/**
|
|
175
181
|
* The role-shaped principal the splice layer actually writes/matches.
|
|
176
|
-
* - group →
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
182
|
+
* - group → the explicit `group/<Name>` token. Never the bare name:
|
|
183
|
+
* a bare name is the ROLE whenever one of that name exists,
|
|
184
|
+
* so a bare group grant could silently mean someone else.
|
|
185
|
+
* - role → the explicit `role/<Name>` token. Bare spellings in
|
|
186
|
+
* existing files remain read-compatible. The built-in
|
|
187
|
+
* `everyone` stays bare: it is not a roles.yaml role and
|
|
188
|
+
* has no `role/` alias.
|
|
181
189
|
*/
|
|
182
190
|
const asSplicePrincipal = (p: RoutePrincipal): Principal => {
|
|
183
|
-
if (p.kind === 'group') return { kind: 'role', role: p.group };
|
|
191
|
+
if (p.kind === 'group') return { kind: 'role', role: `${GROUP_TOKEN_PREFIX}${groupName(p.group)}` };
|
|
184
192
|
// Written with the folder's own casing; `parseAccessEntry` canonicalises
|
|
185
193
|
// the name half to the manifest slug on the way back in.
|
|
186
194
|
if (p.kind === 'plugin') return { kind: 'role', role: `${PLUGIN_TOKEN_PREFIX}${p.plugin.trim()}/${p.verb}` };
|
|
@@ -209,6 +217,14 @@ export function createAccessRoutes(
|
|
|
209
217
|
return p;
|
|
210
218
|
};
|
|
211
219
|
|
|
220
|
+
/** A group as the caller named it, without a `group/` prefix it may carry (casing kept). */
|
|
221
|
+
function groupName(group: string): string {
|
|
222
|
+
const raw = group.trim();
|
|
223
|
+
return splitPrincipalToken(canonicalRoleName(raw)).prefix === 'group'
|
|
224
|
+
? raw.slice(GROUP_TOKEN_PREFIX.length).trim()
|
|
225
|
+
: raw;
|
|
226
|
+
}
|
|
227
|
+
|
|
212
228
|
// Authentication gate only. Per PLAN §3 the workspace `:id` is a branch
|
|
213
229
|
// identifier — any authenticated user can read its access tree.
|
|
214
230
|
async function requireUser(
|
|
@@ -511,8 +527,8 @@ export function createAccessRoutes(
|
|
|
511
527
|
* signed in — so the dialog can label them; it withholds nobody.
|
|
512
528
|
*
|
|
513
529
|
* A name shared by a group and a role is offered as BOTH — nothing is
|
|
514
|
-
* withheld:
|
|
515
|
-
*
|
|
530
|
+
* withheld: each is written with its own prefix (`group/<Name>`,
|
|
531
|
+
* `role/<Name>`), so the collision never reaches the file.
|
|
516
532
|
*/
|
|
517
533
|
router.get('/workspace/:id/access/suggest', async (req, res) => {
|
|
518
534
|
const user = await requireUser(req, res);
|
|
@@ -948,10 +964,10 @@ export function createAccessRoutes(
|
|
|
948
964
|
// uses — and one the caller may see (`visibleGroups`): a group hidden
|
|
949
965
|
// from the picker is refused here with the same words as one that
|
|
950
966
|
// does not exist, so the grant route is no oracle for group names.
|
|
951
|
-
// No role-collision refusal:
|
|
952
|
-
//
|
|
967
|
+
// No role-collision refusal: the grant is written `group/<Name>`,
|
|
968
|
+
// which always resolves to the group.
|
|
953
969
|
const groups = await visibleGroups(user.email, (await defaultBranchPrincipals()).groups);
|
|
954
|
-
const canonical = canonicalRoleName(principal.group);
|
|
970
|
+
const canonical = canonicalRoleName(groupName(principal.group));
|
|
955
971
|
if (!groups.some((g) => canonicalRoleName(g) === canonical)) {
|
|
956
972
|
throw new AccessMutationError(
|
|
957
973
|
`No group named "${principal.group}". Pick an existing group.`,
|
|
@@ -976,8 +992,8 @@ export function createAccessRoutes(
|
|
|
976
992
|
);
|
|
977
993
|
}
|
|
978
994
|
}
|
|
979
|
-
// Splice shape: group →
|
|
980
|
-
//
|
|
995
|
+
// Splice shape: group → explicit `group/<Name>` token; role → explicit
|
|
996
|
+
// `role/<Name>` token (see asSplicePrincipal).
|
|
981
997
|
const splicePrincipal = asSplicePrincipal(principal);
|
|
982
998
|
|
|
983
999
|
const { gatePath } = gateAndEditPaths(kind, repoRelTarget);
|
|
@@ -1045,18 +1061,12 @@ export function createAccessRoutes(
|
|
|
1045
1061
|
try {
|
|
1046
1062
|
const mode = (req.body as { mode?: unknown }).mode;
|
|
1047
1063
|
const { repoRelTarget, kind, principal: routePrincipal } = parseMutationBody(req.body);
|
|
1048
|
-
// A group principal converts straight to
|
|
1049
|
-
//
|
|
1050
|
-
//
|
|
1064
|
+
// A group principal converts straight to its `group/<Name>` token — no
|
|
1065
|
+
// existence check (revoking a grant whose group has since vanished must
|
|
1066
|
+
// keep working). Which spellings a revoke strips is the mutation
|
|
1067
|
+
// service's call: `group/<Name>` never matches `role/<Name>`, and the
|
|
1068
|
+
// bare name only when no role of that name owns it.
|
|
1051
1069
|
const principal = asSplicePrincipal(routePrincipal);
|
|
1052
|
-
// GROUP revokes match the EXACT bare token only. The service's own
|
|
1053
|
-
// shadow probe decides exact-vs-name for roles, but it cannot know a
|
|
1054
|
-
// bare token was a GROUP once that group vanished from the active
|
|
1055
|
-
// source — the name would read "unshadowed" and name-level matching
|
|
1056
|
-
// would strip a same-named role's live `role/<Name>` grant. The route
|
|
1057
|
-
// still knows the caller's kind, so it pins the matching here.
|
|
1058
|
-
const revokeOpts =
|
|
1059
|
-
routePrincipal.kind === 'group' ? ({ tokenMatch: 'exact' } as const) : undefined;
|
|
1060
1070
|
// Optional `verb` scopes ALL three paths to a single verb: a default revoke
|
|
1061
1071
|
// strips just that verb; remove-from-parent removes just that verb on the
|
|
1062
1072
|
// ancestor; deny-here denies just that verb at the target. Absent ⇒ the
|
|
@@ -1112,7 +1122,7 @@ export function createAccessRoutes(
|
|
|
1112
1122
|
);
|
|
1113
1123
|
}
|
|
1114
1124
|
// Scope the ancestor revoke to the same verb the user acted on (if any).
|
|
1115
|
-
await mutation.revoke(workspaceId, 'folder', ancestorDir, principal, user.email, verb
|
|
1125
|
+
await mutation.revoke(workspaceId, 'folder', ancestorDir, principal, user.email, verb);
|
|
1116
1126
|
});
|
|
1117
1127
|
|
|
1118
1128
|
accessControl.invalidate(workspaceId);
|
|
@@ -1131,7 +1141,7 @@ export function createAccessRoutes(
|
|
|
1131
1141
|
await mutation.assertTargetHoldsText(workspaceId, kind, repoRelTarget);
|
|
1132
1142
|
await withEditLock(workspaceId, branch, gatePath, user, async () => {
|
|
1133
1143
|
// Scope the deny to the same verb the user acted on (if any).
|
|
1134
|
-
await mutation.denyHere(workspaceId, kind, repoRelTarget, principal, verb
|
|
1144
|
+
await mutation.denyHere(workspaceId, kind, repoRelTarget, principal, verb);
|
|
1135
1145
|
});
|
|
1136
1146
|
accessControl.invalidate(workspaceId);
|
|
1137
1147
|
logger('access.revoke.deny-here').info(
|
|
@@ -1150,7 +1160,7 @@ export function createAccessRoutes(
|
|
|
1150
1160
|
const editPath = gatePath;
|
|
1151
1161
|
let changed = false;
|
|
1152
1162
|
await withEditLock(workspaceId, branch, editPath, user, async () => {
|
|
1153
|
-
const r = await mutation.revoke(workspaceId, kind, repoRelTarget, principal, user.email, verb
|
|
1163
|
+
const r = await mutation.revoke(workspaceId, kind, repoRelTarget, principal, user.email, verb);
|
|
1154
1164
|
changed = r.changed;
|
|
1155
1165
|
});
|
|
1156
1166
|
|
|
@@ -1247,7 +1257,7 @@ export function createAccessRoutes(
|
|
|
1247
1257
|
// surface). The frontend hides the page, but the API must gate too — the
|
|
1248
1258
|
// roster exposes every role + member email.
|
|
1249
1259
|
await assertRolesAdmin(user.email);
|
|
1250
|
-
res.json({ roles: await rolesAdmin.getRoster() });
|
|
1260
|
+
res.json({ roles: await rolesAdmin.getRoster(user.email) });
|
|
1251
1261
|
} catch (err) {
|
|
1252
1262
|
const { status, body } = toHttpError(err);
|
|
1253
1263
|
res.status(status).json(body);
|
|
@@ -50,9 +50,11 @@ export function createGroupsAdminRoutes(deps: {
|
|
|
50
50
|
// role assignments) + `groupsHealth` (the broken-source banner marker; a
|
|
51
51
|
// broken MANUAL groups.yaml instead answers 422 with the parse message so
|
|
52
52
|
// the operator sees what to fix).
|
|
53
|
-
router.get('/admin/groups', requireAdmin, async (
|
|
53
|
+
router.get('/admin/groups', requireAdmin, async (req, res) => {
|
|
54
54
|
try {
|
|
55
|
-
|
|
55
|
+
// A read needs no commit attribution: the roster is filtered for the
|
|
56
|
+
// email `requireAdmin` just checked, with no second user lookup to fail.
|
|
57
|
+
res.json(await groupsAdmin.getRoster(req.userEmail!));
|
|
56
58
|
} catch (err) {
|
|
57
59
|
sendError(res, err);
|
|
58
60
|
}
|