@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
|
@@ -3,6 +3,7 @@ import cors from 'cors';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import type { Router, RequestHandler } from 'express';
|
|
5
5
|
import { createAuthRoutes } from '../modules/auth/auth.routes.js';
|
|
6
|
+
import { authCookieSecure } from '../modules/auth/auth.middleware.js';
|
|
6
7
|
import { createWorkspaceRoutes } from '../modules/workspace/workspace.routes.js';
|
|
7
8
|
import {
|
|
8
9
|
createAgentUploadRoutes,
|
|
@@ -364,6 +365,9 @@ export async function createCoreServer(
|
|
|
364
365
|
core.authMiddleware,
|
|
365
366
|
core.authProviders,
|
|
366
367
|
core.config.loginPasswordEnabled,
|
|
368
|
+
// The cookie's Secure flag follows the scheme the deployment is reached
|
|
369
|
+
// on, as the SSO providers' does: one rule for every sign-in path.
|
|
370
|
+
{ cookieSecure: authCookieSecure(core.config.publicBackendUrl) },
|
|
367
371
|
),
|
|
368
372
|
);
|
|
369
373
|
|
|
@@ -383,6 +387,7 @@ export async function createCoreServer(
|
|
|
383
387
|
core.internalTokenService,
|
|
384
388
|
core.mcpOAuthProvider,
|
|
385
389
|
core.mcpResourceMetadataUrl,
|
|
390
|
+
core.agentScopeService,
|
|
386
391
|
));
|
|
387
392
|
|
|
388
393
|
// Remote sync — `POST /api/sync`, called by a git host's webhook or a
|
|
@@ -423,6 +428,7 @@ export async function createCoreServer(
|
|
|
423
428
|
toolManualService: core.toolManualService,
|
|
424
429
|
accessControl: core.accessControl,
|
|
425
430
|
connectionProbe: core.connectionProbeService,
|
|
431
|
+
agents: core.agentScopeService,
|
|
426
432
|
stateSecret: core.config.jwtSecret,
|
|
427
433
|
publicBackendUrl: core.config.publicBackendUrl,
|
|
428
434
|
publicFrontendUrl: core.config.publicFrontendUrl,
|
|
@@ -552,6 +558,7 @@ export async function createCoreServer(
|
|
|
552
558
|
core.manualAuthMiddleware,
|
|
553
559
|
async (userId) => (await core.authService.getUserById(userId))?.email,
|
|
554
560
|
readOnlyHints,
|
|
561
|
+
core.agentScopeService,
|
|
555
562
|
));
|
|
556
563
|
toolsRouter.use(createToolHintsAgentRoutes(readOnlyHints, core.manualAuthMiddleware));
|
|
557
564
|
// The aggregated manual list (KB + the caller's accessible `.tool`s) + inline
|
|
@@ -568,6 +575,7 @@ export async function createCoreServer(
|
|
|
568
575
|
disk: core.disk,
|
|
569
576
|
pluginIndex: core.pluginIndexService,
|
|
570
577
|
},
|
|
578
|
+
core.agentScopeService,
|
|
571
579
|
));
|
|
572
580
|
// What every connected agent is told at session start, as the hosted proxy
|
|
573
581
|
// composes it: read by the local `hexis-mcp` bridge at startup and by the
|
|
@@ -582,6 +590,7 @@ export async function createCoreServer(
|
|
|
582
590
|
skills: core.skillService,
|
|
583
591
|
manualAuth: core.manualAuthMiddleware,
|
|
584
592
|
resolveUserEmail: async (userId) => (await core.authService.getUserById(userId))?.email,
|
|
593
|
+
agentScope: core.agentScopeService,
|
|
585
594
|
// The views move the revision too: a release that changes a view and no
|
|
586
595
|
// manual would otherwise leave a running local server on the old one.
|
|
587
596
|
mcpApps: core.mcpAppService,
|
|
@@ -596,6 +605,7 @@ export async function createCoreServer(
|
|
|
596
605
|
core.secretsVaultService,
|
|
597
606
|
core.manualAuthMiddleware,
|
|
598
607
|
async (userId) => (await core.authService.getUserById(userId))?.email,
|
|
608
|
+
core.agentScopeService,
|
|
599
609
|
));
|
|
600
610
|
app.use('/api', toolsRouter);
|
|
601
611
|
|
|
@@ -638,6 +648,7 @@ export async function createCoreServer(
|
|
|
638
648
|
createPluginCreationRoutes(
|
|
639
649
|
core.pluginProvisionService,
|
|
640
650
|
async (req) => (req.userId ? ((await core.authService.getUserById(req.userId)) ?? null) : null),
|
|
651
|
+
core.agentScopeService,
|
|
641
652
|
),
|
|
642
653
|
);
|
|
643
654
|
|
|
@@ -705,6 +716,7 @@ export async function createCoreServer(
|
|
|
705
716
|
core.eventBus,
|
|
706
717
|
core.db,
|
|
707
718
|
core.kb,
|
|
719
|
+
core.disk,
|
|
708
720
|
[core.config.adminEmail],
|
|
709
721
|
));
|
|
710
722
|
// Asking for Can edit or Owner on an item from its Manage access dialog, and
|
|
@@ -904,6 +916,7 @@ export async function createCoreServer(
|
|
|
904
916
|
clientNameFor: (st) => core.githubFacade.clientNameFor(st),
|
|
905
917
|
completeConsent: (userId, st) => core.githubFacade.completeConsent(userId, st),
|
|
906
918
|
},
|
|
919
|
+
agents: core.agentScopeService,
|
|
907
920
|
}));
|
|
908
921
|
|
|
909
922
|
// What an Owner pastes into Claude's admin settings to register this
|
|
@@ -63,32 +63,38 @@ function parseDomainList(raw: string): string[] {
|
|
|
63
63
|
.filter((d) => d.length > 0);
|
|
64
64
|
}
|
|
65
65
|
import { SpillStore } from '../modules/workspace/spill-store.js';
|
|
66
|
+
import { TmpSweeper } from '../modules/workspace/workspace-tmp.js';
|
|
66
67
|
import { AgentUploadStore, assertUploadsRootOutsideWorkspaces } from '../modules/workspace/agent-upload.store.js';
|
|
67
68
|
import { AgentDownloadStore, type IAgentDownloadStore } from '../modules/workspace/agent-download.store.js';
|
|
68
69
|
import { createDownloadFetcherIdentifier } from '../modules/workspace/agent-download.routes.js';
|
|
69
|
-
import { WorkerPatternMatcher } from '../modules/workspace/bounded-matcher.js';
|
|
70
|
-
import type { IPatternMatcher } from '../modules/workspace/pattern-matcher.contract.js';
|
|
71
70
|
import type { Request } from 'express';
|
|
72
71
|
import {
|
|
72
|
+
assertRipgrep,
|
|
73
73
|
DocExtractService,
|
|
74
|
+
RipgrepPatternMatcher,
|
|
74
75
|
WorkerDocumentExtractor,
|
|
75
76
|
type IDocumentExtractor,
|
|
77
|
+
type IPatternMatcher,
|
|
76
78
|
} from '../modules/workspace/index.js';
|
|
77
79
|
import { UuidSessionSink, type ISessionSink } from '../modules/workspace/session-sink.js';
|
|
78
80
|
import { AuthService } from '../modules/auth/auth.service.js';
|
|
79
81
|
import { AccountHooks, type IAccountHooks } from '../modules/auth/account-hooks.js';
|
|
80
82
|
import { AccountErasureService } from '../modules/auth/account-erasure.service.js';
|
|
81
83
|
import { OidcAuthProvider, oidcSettingsFrom } from '../modules/auth/oidc-auth-provider.js';
|
|
82
|
-
import { createAuthMiddleware, readAuthCookie } from '../modules/auth/auth.middleware.js';
|
|
84
|
+
import { authCookieSecure, createAuthMiddleware, readAuthCookie } from '../modules/auth/auth.middleware.js';
|
|
83
85
|
import { AccessControlService, loadActiveGroups } from '../modules/access/access-control.service.js';
|
|
84
86
|
import { AccessMutationService } from '../modules/access/access-mutation.service.js';
|
|
85
87
|
import { CreatorAccessService } from '../modules/access/creator-access.js';
|
|
86
88
|
import { ChangeReadGate } from '../modules/access/change-read-gate.js';
|
|
87
89
|
import { GroupsAdminService } from '../modules/access/groups-admin.service.js';
|
|
88
90
|
import { UserAccessRemovalService } from '../modules/access/user-access-removal.service.js';
|
|
89
|
-
import { makeRolesYamlWriteValidator } from '../modules/access-model/
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
import { makeRolesYamlWriteValidator } from '../modules/access-model/index.js';
|
|
92
|
+
// The implementations, from their files: the module entries offer them as
|
|
93
|
+
// types only, so nothing that imports an entry loads a service.
|
|
94
|
+
import { SkillService } from '../modules/skills/skills.service.js';
|
|
95
|
+
import { PendingSkillsService } from '../modules/skills/pending-skills.service.js';
|
|
96
|
+
import { ToolManualService } from '../modules/tool-manuals/tool-manuals.service.js';
|
|
97
|
+
import { PendingToolsService } from '../modules/tool-manuals/pending-tools.service.js';
|
|
92
98
|
import { McpServerEditService } from '../modules/tool-manuals/mcp-server-edit.service.js';
|
|
93
99
|
import { ToolDeleteService } from '../modules/tool-manuals/tool-delete.service.js';
|
|
94
100
|
import {
|
|
@@ -105,6 +111,7 @@ import {
|
|
|
105
111
|
MarketplaceCompilerService,
|
|
106
112
|
KbPluginSource,
|
|
107
113
|
} from '../modules/plugins/index.js';
|
|
114
|
+
import { noAgentConfigurations, type IAgentScopeResolver } from '../modules/agent-scope/index.js';
|
|
108
115
|
import { MarketplaceRepoService } from '../modules/marketplace/index.js';
|
|
109
116
|
import {
|
|
110
117
|
GitHubFacadeCredentialsService,
|
|
@@ -255,7 +262,9 @@ export interface CoreServices {
|
|
|
255
262
|
agentUploadStore: AgentUploadStore;
|
|
256
263
|
/** The bytes `request_file_download` captured, held until their one-time link is fetched or expires. */
|
|
257
264
|
agentDownloadStore: IAgentDownloadStore;
|
|
258
|
-
/**
|
|
265
|
+
/** Sweeps every workspace's `tmp/` folder of entries older than seven days, hourly; started and stopped with the graph. */
|
|
266
|
+
tmpSweeper: TmpSweeper;
|
|
267
|
+
/** Runs a caller's `grep` pattern in ripgrep under a budget; its running searches end when the graph stops. */
|
|
259
268
|
patternMatcher: IPatternMatcher;
|
|
260
269
|
/**
|
|
261
270
|
* Every user a download fetch identifies itself as, by its bearer and its
|
|
@@ -309,6 +318,12 @@ export interface CoreServices {
|
|
|
309
318
|
pluginJoinRequestJobs: PluginJoinRequestJobs;
|
|
310
319
|
/** Which plugins hold which skills (inline or linked) — see `PluginLinkIndex`. */
|
|
311
320
|
pluginLinkIndex: PluginLinkIndex;
|
|
321
|
+
/**
|
|
322
|
+
* Connecting as an agent configuration: the choices offered at consent and key
|
|
323
|
+
* mint, and the scope every agent surface narrows a connection to — see
|
|
324
|
+
* `modules/agent-scope`.
|
|
325
|
+
*/
|
|
326
|
+
agentScopeService: IAgentScopeResolver;
|
|
312
327
|
/** Link / unlink / repair shared skills into plugins. */
|
|
313
328
|
pluginLinksService: PluginLinksService;
|
|
314
329
|
pluginRenameService: PluginRenameService;
|
|
@@ -432,6 +447,9 @@ export async function createCoreServices(
|
|
|
432
447
|
// (see `git-version.ts`): every clone, fetch and refresh below depends on it,
|
|
433
448
|
// so an unsupported binary is better surfaced here than at the first merge.
|
|
434
449
|
await assertGitVersion();
|
|
450
|
+
// The same for ripgrep, which the grep tool searches with and has no
|
|
451
|
+
// fallback for (see `ripgrep-version.ts`).
|
|
452
|
+
await assertRipgrep();
|
|
435
453
|
// What tells this knowledge base's locks, migration ledger and secrets
|
|
436
454
|
// loader apart from another's in the same process and database. Empty for
|
|
437
455
|
// the default schema, so a single-tenant deployment keeps the lock ids and
|
|
@@ -767,6 +785,10 @@ export async function createCoreServices(
|
|
|
767
785
|
// Shared, workspace-independent store for oversized `call_tool_chain` results,
|
|
768
786
|
// read back via `read_file`. Sibling of `workspacesRoot`, never committed.
|
|
769
787
|
const spillStore = new SpillStore(config.spillRoot);
|
|
788
|
+
// What the chat, the SharePoint reader and tool chains stage in a
|
|
789
|
+
// workspace's `tmp/` folder lives seven days past its last change; the file
|
|
790
|
+
// tools read it through the `__tmp__/` ref meanwhile (see workspace-tmp.ts).
|
|
791
|
+
const tmpSweeper = new TmpSweeper((signal) => workspaceService.sweepTmp(undefined, undefined, signal));
|
|
770
792
|
// The upload route an agent lands files by, so their content never passes
|
|
771
793
|
// through the model. Bytes live BESIDE the workspaces root (never inside
|
|
772
794
|
// one) until the apply commits them or the token expires — checked here
|
|
@@ -795,7 +817,7 @@ export async function createCoreServices(
|
|
|
795
817
|
'captured download bytes wait there for their one-time links, so a root inside a workspace would let the ' +
|
|
796
818
|
'file tools read them. Point AGENT_UPLOADS_ROOT at a directory whose parent is outside WORKSPACES_ROOT.',
|
|
797
819
|
});
|
|
798
|
-
const patternMatcher = new
|
|
820
|
+
const patternMatcher = new RipgrepPatternMatcher();
|
|
799
821
|
const agentDownloadStore = new AgentDownloadStore({
|
|
800
822
|
root: agentDownloadsRoot,
|
|
801
823
|
publicBaseUrl: config.publicBackendUrl,
|
|
@@ -851,6 +873,17 @@ export async function createCoreServices(
|
|
|
851
873
|
// The link index resolves manifests against the released catalog, and the
|
|
852
874
|
// plugin index counts through it (inline + linked), so it comes first.
|
|
853
875
|
const pluginLinkIndex = new PluginLinkIndex(workspaceService, skillService, accessControl, kb, pluginSource);
|
|
876
|
+
// What "Connect as" offers and what a connection made as an agent loads:
|
|
877
|
+
// the distribution's resolver (`CorePorts.agentScope`), made HERE with the
|
|
878
|
+
// core services it reads through — its configurations behind the person's
|
|
879
|
+
// read gate, `plugins:` expanded through the link index above — or core's
|
|
880
|
+
// default, which offers no agent and loads nothing for a path it cannot
|
|
881
|
+
// read. Made once, before anything consults it: the tool-context resolver
|
|
882
|
+
// below captures it, as the routes the server mounts do, so there is one
|
|
883
|
+
// resolver in effect and no later assignment to miss. Core owns the
|
|
884
|
+
// mechanism and no configuration (see `modules/agent-scope`).
|
|
885
|
+
const agentScopeService: IAgentScopeResolver =
|
|
886
|
+
ports.agentScope?.({ workspaceService, accessControl, kb, disk, pluginLinkIndex }) ?? noAgentConfigurations();
|
|
854
887
|
const pluginIndexService = new PluginIndexService(
|
|
855
888
|
workspaceService,
|
|
856
889
|
accessControl,
|
|
@@ -1487,6 +1520,7 @@ export async function createCoreServices(
|
|
|
1487
1520
|
kbDirName: kbDirName,
|
|
1488
1521
|
creatorAccess,
|
|
1489
1522
|
loadActiveGroups,
|
|
1523
|
+
agentScope: agentScopeService,
|
|
1490
1524
|
});
|
|
1491
1525
|
const writeAccess = ports.writeAccess ?? alwaysWritable;
|
|
1492
1526
|
// The branch every tool call runs on is resolved by the handler, before the
|
|
@@ -1496,8 +1530,8 @@ export async function createCoreServices(
|
|
|
1496
1530
|
defaultBranch: () => kb.defaultBranch,
|
|
1497
1531
|
isMissing: (branch) => workspaceService.isBranchMissing(branch),
|
|
1498
1532
|
});
|
|
1499
|
-
const toolAuthMiddleware = createToolAuthMiddleware(externalApiKeyService, internalTokenService, authService);
|
|
1500
|
-
const verifyToolToken = createTokenVerifier(externalApiKeyService, internalTokenService, authService);
|
|
1533
|
+
const toolAuthMiddleware = createToolAuthMiddleware(externalApiKeyService, internalTokenService, authService, mcpOAuthProvider);
|
|
1534
|
+
const verifyToolToken = createTokenVerifier(externalApiKeyService, internalTokenService, authService, mcpOAuthProvider);
|
|
1501
1535
|
// Every credential kind this server issues — connection keys, internal
|
|
1502
1536
|
// tokens and app sessions (bearer or cookie) — so a fetch carrying another
|
|
1503
1537
|
// user's identity of ANY kind is refused the link.
|
|
@@ -1508,7 +1542,7 @@ export async function createCoreServices(
|
|
|
1508
1542
|
// Read-only manual endpoints accept the above PLUS a browser JWT, so a
|
|
1509
1543
|
// logged-in user can browse the catalog with their session. Execution routes
|
|
1510
1544
|
// keep `toolAuthMiddleware` (no JWT), so a session can read but not invoke.
|
|
1511
|
-
const manualAuthMiddleware = createManualAuthMiddleware(externalApiKeyService, internalTokenService, authService);
|
|
1545
|
+
const manualAuthMiddleware = createManualAuthMiddleware(externalApiKeyService, internalTokenService, authService, mcpOAuthProvider);
|
|
1512
1546
|
|
|
1513
1547
|
// Recovery-bot identity: a synthetic `recovery-bot@bevel.local` user
|
|
1514
1548
|
// backing (a) the recovery agent's commit authorship and (b) the
|
|
@@ -1575,7 +1609,7 @@ export async function createCoreServices(
|
|
|
1575
1609
|
settings: () => oidcSettingsFrom(settings),
|
|
1576
1610
|
publicBackendUrl: config.publicBackendUrl,
|
|
1577
1611
|
publicFrontendUrl: config.publicFrontendUrl,
|
|
1578
|
-
cookieSecure: config.publicBackendUrl
|
|
1612
|
+
cookieSecure: authCookieSecure(config.publicBackendUrl),
|
|
1579
1613
|
// A real sign-in proves the values that sign-in used. Recorded against
|
|
1580
1614
|
// those — under their own key — so it says nothing about any saved
|
|
1581
1615
|
// since, and cannot overwrite what was recorded about them.
|
|
@@ -1619,6 +1653,7 @@ export async function createCoreServices(
|
|
|
1619
1653
|
workspaceService,
|
|
1620
1654
|
workflowService,
|
|
1621
1655
|
accessControl,
|
|
1656
|
+
disk,
|
|
1622
1657
|
kbDirName,
|
|
1623
1658
|
() => kb.defaultBranch,
|
|
1624
1659
|
eventBus,
|
|
@@ -1673,6 +1708,7 @@ export async function createCoreServices(
|
|
|
1673
1708
|
spillStore,
|
|
1674
1709
|
agentUploadStore,
|
|
1675
1710
|
agentDownloadStore,
|
|
1711
|
+
tmpSweeper,
|
|
1676
1712
|
patternMatcher,
|
|
1677
1713
|
agentDownloadFetcher,
|
|
1678
1714
|
documentExtractor,
|
|
@@ -1694,6 +1730,7 @@ export async function createCoreServices(
|
|
|
1694
1730
|
joinRequestsService,
|
|
1695
1731
|
pluginJoinRequestJobs,
|
|
1696
1732
|
pluginLinkIndex,
|
|
1733
|
+
agentScopeService,
|
|
1697
1734
|
pluginLinksService,
|
|
1698
1735
|
pluginRenameService,
|
|
1699
1736
|
marketplaceCompiler,
|
package/src/core/lifecycle.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { noteBesideCheckout } from '../modules/workspace/startup/beside-checkout
|
|
|
6
6
|
import { unregisterBevelSecretsVariableLoader } from '../modules/secrets-vault/secrets-variable-loader.js';
|
|
7
7
|
import type { WorkflowService } from '../modules/workflow/workflow.service.js';
|
|
8
8
|
import type { PluginJoinRequestJobs } from '../modules/plugins/join-request-jobs.service.js';
|
|
9
|
+
import type { TmpSweeper } from '../modules/workspace/workspace-tmp.js';
|
|
9
10
|
import { printable } from '../shared/printable.js';
|
|
10
11
|
import { logger } from '../shared/logging.js';
|
|
11
12
|
|
|
@@ -293,6 +294,8 @@ export interface BootableCore {
|
|
|
293
294
|
kbStartupRunner: Pick<KbStartupRunner, 'runAll' | 'retryUntilMaintained'>;
|
|
294
295
|
workflowService: Pick<WorkflowService, 'closeChangeRequestsWithDeletedBranches' | 'tidyAfterSweep'>;
|
|
295
296
|
pluginJoinRequestJobs: Pick<PluginJoinRequestJobs, 'startSweeping'>;
|
|
297
|
+
/** The periodic sweep of every workspace's `tmp/` folder, started here and stopped by {@link stopCore}. */
|
|
298
|
+
tmpSweeper: Pick<TmpSweeper, 'startSweeping'>;
|
|
296
299
|
/**
|
|
297
300
|
* The startup phase's retry, when the boot survived an unreachable remote
|
|
298
301
|
* and is asking again on a timer; null otherwise. Set by {@link startCore},
|
|
@@ -336,6 +339,13 @@ export async function startCore<C extends BootableCore>(
|
|
|
336
339
|
});
|
|
337
340
|
}
|
|
338
341
|
|
|
342
|
+
// The seven-day sweep of every workspace's `tmp/` folder (see
|
|
343
|
+
// `workspace-tmp.ts`), now and hourly. Needs nothing the startup phase
|
|
344
|
+
// provides — it reads the workspaces root and nothing else — so it is not
|
|
345
|
+
// held behind it, and a remote that is down does not leave staged files to
|
|
346
|
+
// pile up. Stopped by `stopCore`.
|
|
347
|
+
core.tmpSweeper.startSweeping();
|
|
348
|
+
|
|
339
349
|
// The KB startup phase — AFTER the distribution's onBoot, because a FATAL
|
|
340
350
|
// template finding raised there must stop the boot before anything seeds
|
|
341
351
|
// from that template; the runner then brings every branch up to this build
|
|
@@ -448,6 +458,16 @@ export interface CoreStopDeps {
|
|
|
448
458
|
agentUploadStore?: { stopSweeping(): void; drainSweep(): Promise<void> };
|
|
449
459
|
/** The agent download store, whose sweep deletes captured bytes nobody fetched — stopped for the same reason. */
|
|
450
460
|
agentDownloadStore?: { stopSweeping(): void; drainSweep(): Promise<void> };
|
|
461
|
+
/**
|
|
462
|
+
* The sweep of the workspaces' `tmp/` folders: stopped with this graph, so
|
|
463
|
+
* an evicted tenant's graph leaves no timer behind, and its sweep in flight
|
|
464
|
+
* cancelled (it removes nothing more) and then awaited within the stop's
|
|
465
|
+
* deadline, so the next graph over the same root does not meet it still
|
|
466
|
+
* walking there — unless one filesystem call outlasts that deadline, after
|
|
467
|
+
* which the cancelled sweep still removes nothing. Optional: a caller that
|
|
468
|
+
* has not built one passes nothing.
|
|
469
|
+
*/
|
|
470
|
+
tmpSweeper?: { stopSweeping(): void; drainSweep(): Promise<void> };
|
|
451
471
|
/**
|
|
452
472
|
* The grep pattern matcher, whose worker threads are this graph's own: ended
|
|
453
473
|
* here so a graph a tenant host evicts leaves no worker behind. Optional: a
|
|
@@ -555,6 +575,7 @@ async function releaseCore(deps: CoreStopDeps, remaining: () => number, log: (m:
|
|
|
555
575
|
// running to stop deleting.
|
|
556
576
|
deps.agentUploadStore?.stopSweeping();
|
|
557
577
|
deps.agentDownloadStore?.stopSweeping();
|
|
578
|
+
deps.tmpSweeper?.stopSweeping();
|
|
558
579
|
// A startup phase still asking for an unreachable remote stops asking:
|
|
559
580
|
// synchronous, and nothing after it must be able to clone into a
|
|
560
581
|
// workspaces folder that is about to belong to nobody.
|
|
@@ -587,15 +608,18 @@ async function releaseCore(deps: CoreStopDeps, remaining: () => number, log: (m:
|
|
|
587
608
|
if (deps.agentDownloadStore) {
|
|
588
609
|
await bounded(deps.agentDownloadStore.drainSweep(), remaining(), 'finishing the download sweep', log);
|
|
589
610
|
}
|
|
611
|
+
if (deps.tmpSweeper) {
|
|
612
|
+
await bounded(deps.tmpSweeper.drainSweep(), remaining(), "finishing the workspaces' tmp sweep", log);
|
|
613
|
+
}
|
|
590
614
|
// The tidy-up in flight fetches and deletes branches through the pool and
|
|
591
615
|
// the default branch's checkout: awaited, so the next graph for this
|
|
592
616
|
// tenant cannot find a branch deletion still running under it.
|
|
593
617
|
if (deps.workflowService) {
|
|
594
618
|
await bounded(deps.workflowService.drainTidy(), remaining(), 'finishing the branch tidy-up', log);
|
|
595
619
|
}
|
|
596
|
-
// Its
|
|
620
|
+
// Its ripgrep runs end with the graph; a search still running is answered busy or stopped.
|
|
597
621
|
if (deps.patternMatcher) {
|
|
598
|
-
await bounded(deps.patternMatcher.close(), remaining(), 'ending the
|
|
622
|
+
await bounded(deps.patternMatcher.close(), remaining(), 'ending the running searches', log);
|
|
599
623
|
}
|
|
600
624
|
// Likewise: an extraction still running is answered busy, and nothing about its file is remembered.
|
|
601
625
|
if (deps.documentExtractor) {
|
package/src/index.ts
CHANGED
|
@@ -26,7 +26,7 @@ export {
|
|
|
26
26
|
type ServerExtensions,
|
|
27
27
|
type ToolSurfaceCtx,
|
|
28
28
|
} from './core/create-core-server.js';
|
|
29
|
-
export { noopRecoveryAgent, type CorePorts } from './core/core-ports.js';
|
|
29
|
+
export { noopRecoveryAgent, type AgentScopeCollaborators, type CorePorts } from './core/core-ports.js';
|
|
30
30
|
// Logging: the contract, the slot a shell installs its sink into, and the
|
|
31
31
|
// tagged-logger lookup every module logs through. See shared/logging.ts.
|
|
32
32
|
export { setLogger, logger, createConsoleLogger } from './shared/logging.js';
|
|
@@ -242,6 +242,17 @@ describe('Admin write floor at the repository root', () => {
|
|
|
242
242
|
expect(await fs.readFile(path.join(repo, 'access.md'), 'utf-8')).toContain('write:\n - Admin');
|
|
243
243
|
});
|
|
244
244
|
|
|
245
|
+
it('lets the root drop a GROUP named Admin — group/Admin is never the role', async () => {
|
|
246
|
+
await write(repo, 'access.md', '---\n---\nwrite:\n - Admin\n - group/Admin\n');
|
|
247
|
+
const group = { kind: 'role' as const, role: 'group/Admin' };
|
|
248
|
+
await expect(mutation.revoke(WS, 'folder', '', group, ADMIN, 'write')).resolves.toMatchObject({
|
|
249
|
+
changed: true,
|
|
250
|
+
});
|
|
251
|
+
const after = await fs.readFile(path.join(repo, 'access.md'), 'utf-8');
|
|
252
|
+
expect(after).not.toContain('group/Admin');
|
|
253
|
+
expect(after).toContain('write:\n - Admin'); // the role's line is untouched
|
|
254
|
+
});
|
|
255
|
+
|
|
245
256
|
it('refuses deny-here on a PERSON who is an admin at the root, with the reason, and writes nothing', async () => {
|
|
246
257
|
const before = await fs.readFile(path.join(repo, 'access.md'), 'utf-8');
|
|
247
258
|
for (const email of [ADMIN, OWNER]) {
|
|
@@ -310,6 +321,7 @@ describe('Admin write floor at the repository root', () => {
|
|
|
310
321
|
{ emit: () => {} } as unknown as WorkflowEventBus,
|
|
311
322
|
usersDbDouble(),
|
|
312
323
|
testKbContext({ kbDirName: KB }),
|
|
324
|
+
new NodeFs(),
|
|
313
325
|
),
|
|
314
326
|
);
|
|
315
327
|
server = await new Promise<Server>((resolve) => {
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The case table for a name shared by a role and a group (the moved property
|
|
3
|
+
* of decision #315, "a bare name means the role"). Before this change a bare
|
|
4
|
+
* name meant the GROUP when both existed; it now means the ROLE. Exactly the
|
|
5
|
+
* colliding bare spellings change meaning, in grants AND in denials (decided
|
|
6
|
+
* by Razvan on 2026-10-23: a bare denial means the role exactly as a bare
|
|
7
|
+
* grant does; a group denial is written `deny group/<Name>`):
|
|
8
|
+
*
|
|
9
|
+
* rule | role-only member | group-only member
|
|
10
|
+
* -----------------------|-----------------------|----------------------
|
|
11
|
+
* read: Reviewer | was denied → granted | was granted → denied
|
|
12
|
+
* deny Reviewer | was allowed → denied | was denied → allowed
|
|
13
|
+
* read: group/Reviewer | denied (unchanged) | granted (new token)
|
|
14
|
+
* deny group/Reviewer | allowed (unchanged) | denied (new token)
|
|
15
|
+
* read: role/Reviewer | granted (unchanged) | denied (unchanged)
|
|
16
|
+
* deny role/Reviewer | denied (unchanged) | allowed (unchanged)
|
|
17
|
+
*
|
|
18
|
+
* A bare denial never touches the same-named group, so `read: group/Reviewer`
|
|
19
|
+
* beside `deny Reviewer` lets the group in. A name held by only one kind
|
|
20
|
+
* resolves as before. Every file with a colliding bare entry is listed by
|
|
21
|
+
* the groups roster (`bareNameResolvesToRole`) so an Admin can rewrite it as
|
|
22
|
+
* `group/<Name>` or `role/<Name>`.
|
|
23
|
+
*/
|
|
24
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
25
|
+
import fs from 'node:fs/promises';
|
|
26
|
+
import path from 'node:path';
|
|
27
|
+
import os from 'node:os';
|
|
28
|
+
|
|
29
|
+
import { NodeFs } from '../../kb-fs/node-fs.js';
|
|
30
|
+
import type { WorkspaceService } from '../../workspace/index.js';
|
|
31
|
+
import { AccessControlService } from '../access-control.service.js';
|
|
32
|
+
|
|
33
|
+
const KB_DIR = 'knowledge-base';
|
|
34
|
+
const WS = 'ws-colliding-name';
|
|
35
|
+
|
|
36
|
+
const ADMIN = 'admin@x.io';
|
|
37
|
+
const ROLE_MEMBER = 'role-member@x.io';
|
|
38
|
+
const GROUP_MEMBER = 'group-member@x.io';
|
|
39
|
+
const DOC = 'Docs/Plan.md';
|
|
40
|
+
|
|
41
|
+
const ROOT_FILES: Record<string, string> = {
|
|
42
|
+
'roles.yaml': `roles:\n Admin:\n - ${ADMIN}\n Reviewer:\n - ${ROLE_MEMBER}\n`,
|
|
43
|
+
'groups.yaml': `groups:\n Reviewer:\n - ${GROUP_MEMBER}\n`,
|
|
44
|
+
'access.md': '---\nwrite:\n - Admin\n---\nwrite:\n - Admin\n',
|
|
45
|
+
[DOC]: '# Plan\n',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
describe('a name shared by a role and a group: grant and deny case table', () => {
|
|
49
|
+
let root: string;
|
|
50
|
+
let repo: string;
|
|
51
|
+
let svc: AccessControlService;
|
|
52
|
+
|
|
53
|
+
beforeEach(async () => {
|
|
54
|
+
root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-colliding-name-'));
|
|
55
|
+
const workspaceDir = path.join(root, WS);
|
|
56
|
+
repo = path.join(workspaceDir, KB_DIR);
|
|
57
|
+
for (const [rel, text] of Object.entries(ROOT_FILES)) {
|
|
58
|
+
await fs.mkdir(path.dirname(path.join(repo, rel)), { recursive: true });
|
|
59
|
+
await fs.writeFile(path.join(repo, rel), text);
|
|
60
|
+
}
|
|
61
|
+
const workspaceService = {
|
|
62
|
+
getWorkspacePath: async () => workspaceDir,
|
|
63
|
+
ensureRemotesFetched: async () => undefined,
|
|
64
|
+
} as unknown as WorkspaceService;
|
|
65
|
+
svc = new AccessControlService(workspaceService, KB_DIR, new NodeFs());
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
afterEach(async () => {
|
|
69
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/** Who may read DOC when the Docs folder's read list is `entries`. */
|
|
73
|
+
async function readers(entries: string[]): Promise<{ role: boolean; group: boolean }> {
|
|
74
|
+
const body = `read:\n${entries.map((e) => ` - ${e}\n`).join('')}`;
|
|
75
|
+
await fs.writeFile(path.join(repo, 'Docs/access.md'), `---\nwrite:\n - Admin\n---\n${body}`);
|
|
76
|
+
svc.invalidate(WS);
|
|
77
|
+
return {
|
|
78
|
+
role: await svc.canRead(WS, ROLE_MEMBER, DOC),
|
|
79
|
+
group: await svc.canRead(WS, GROUP_MEMBER, DOC),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
it('a bare grant reaches the role only', async () => {
|
|
84
|
+
expect(await readers(['Reviewer'])).toEqual({ role: true, group: false });
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('a bare denial reaches the role only: the members of the same-named group are untouched', async () => {
|
|
88
|
+
expect(await readers(['everyone', 'deny Reviewer'])).toEqual({ role: false, group: true });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('a group grant beside a bare denial lets the group in', async () => {
|
|
92
|
+
expect(await readers(['group/Reviewer', 'deny Reviewer'])).toEqual({ role: false, group: true });
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('group/<Name> grants and denies the group only', async () => {
|
|
96
|
+
expect(await readers(['group/Reviewer'])).toEqual({ role: false, group: true });
|
|
97
|
+
expect(await readers(['everyone', 'deny group/Reviewer'])).toEqual({ role: true, group: false });
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('role/<Name> grants and denies the role only', async () => {
|
|
101
|
+
expect(await readers(['role/Reviewer'])).toEqual({ role: true, group: false });
|
|
102
|
+
expect(await readers(['everyone', 'deny role/Reviewer'])).toEqual({ role: false, group: true });
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('a bare name held only by a group still means the group, in grants and denials', async () => {
|
|
106
|
+
await fs.writeFile(
|
|
107
|
+
path.join(repo, 'roles.yaml'),
|
|
108
|
+
`roles:\n Admin:\n - ${ADMIN}\n`,
|
|
109
|
+
);
|
|
110
|
+
expect(await readers(['Reviewer'])).toEqual({ role: false, group: true });
|
|
111
|
+
expect(await readers(['everyone', 'deny Reviewer'])).toEqual({ role: true, group: false });
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import { NodeFs } from '../../kb-fs/node-fs.js';
|
|
6
|
+
import type { WorkspaceService } from '../../workspace/workspace.service.js';
|
|
7
|
+
import { AccessControlService } from '../access-control.service.js';
|
|
8
|
+
|
|
9
|
+
const KB = 'knowledge-base';
|
|
10
|
+
const RULE = 'read:\n - deny everyone\n - Product Manager\n';
|
|
11
|
+
const ali = 'ali@bevel.software'; // no role
|
|
12
|
+
const felix = 'felix@example.com'; // Product Manager
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A file's own access rules live in its frontmatter, so the read checks read
|
|
16
|
+
* a file only as far as its frontmatter reaches — a line, for a file with
|
|
17
|
+
* none. That is what keeps a batch over a tree of large transcripts (grep's,
|
|
18
|
+
* the explorer's) from reading the whole tree, and these pin that the head
|
|
19
|
+
* answers exactly as the whole file would, fence rule and all.
|
|
20
|
+
*/
|
|
21
|
+
describe('AccessControlService — a file is read only as far as its frontmatter', () => {
|
|
22
|
+
let root: string;
|
|
23
|
+
let repo: string;
|
|
24
|
+
let svc: AccessControlService;
|
|
25
|
+
const workspaceId = 'ws-frontmatter-head';
|
|
26
|
+
|
|
27
|
+
beforeEach(async () => {
|
|
28
|
+
root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-fm-head-'));
|
|
29
|
+
const workspaceDir = path.join(root, workspaceId);
|
|
30
|
+
repo = path.join(workspaceDir, KB);
|
|
31
|
+
await fs.mkdir(path.join(repo, 'Open'), { recursive: true });
|
|
32
|
+
await fs.writeFile(path.join(repo, 'roles.yaml'), 'roles:\n Admin:\n - razvan@bevel.software\n Product Manager:\n - felix@example.com\n');
|
|
33
|
+
await fs.writeFile(path.join(repo, 'Open', 'access.md'), '---\nread:\n - everyone\n---\n');
|
|
34
|
+
svc = new AccessControlService(
|
|
35
|
+
{ getWorkspacePath: async () => workspaceDir } as unknown as WorkspaceService,
|
|
36
|
+
KB,
|
|
37
|
+
new NodeFs(),
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
afterEach(async () => {
|
|
41
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const put = (name: string, content: string | Buffer) => fs.writeFile(path.join(repo, 'Open', name), content);
|
|
45
|
+
const verdicts = async (name: string) => {
|
|
46
|
+
const rel = `Open/${name}`;
|
|
47
|
+
const batch = await svc.canReadBatch(workspaceId, ali, [rel]);
|
|
48
|
+
const felixBatch = await svc.canReadBatch(workspaceId, felix, [rel]);
|
|
49
|
+
return {
|
|
50
|
+
ali: batch.get(rel),
|
|
51
|
+
felix: felixBatch.get(rel),
|
|
52
|
+
single: await svc.canRead(workspaceId, ali, rel),
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
it('honours the rules of a file far too large to read whole, at once', async () => {
|
|
57
|
+
await put('huge.md', `---\n${RULE}---\n`);
|
|
58
|
+
// 600 MB, sparse: more text than one string can hold, so a read of the
|
|
59
|
+
// whole file could not even be decoded, and its rule would be missed.
|
|
60
|
+
await fs.truncate(path.join(repo, 'Open', 'huge.md'), 600 * 1024 * 1024);
|
|
61
|
+
expect(await verdicts('huge.md')).toEqual({ ali: false, felix: true, single: false });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('reads a frontmatter longer than any one read through to its closing fence', async () => {
|
|
65
|
+
await put('long-fm.md', `---\nsummary: "${'x'.repeat(200_000)}"\n${RULE}---\nbody\n`);
|
|
66
|
+
expect(await verdicts('long-fm.md')).toEqual({ ali: false, felix: true, single: false });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('scans a frontmatter line far longer than one read once, not again with every read', async () => {
|
|
70
|
+
// A long line inside the frontmatter, then the rule; and a closing fence
|
|
71
|
+
// padded with 3 MB of spaces, which stays a fence however long it runs.
|
|
72
|
+
const longValue = (chars: number) =>
|
|
73
|
+
`---\nsummary: "${'x'.repeat(chars)}"\n${RULE}${' '.repeat(3_000_000)}---${' '.repeat(3_000_000)}\nbody\n`;
|
|
74
|
+
await put('long-value.md', longValue(32_000_000));
|
|
75
|
+
expect(await verdicts('long-value.md')).toEqual({ ali: false, felix: true, single: false });
|
|
76
|
+
// How the cost grows, not how long it takes on this machine: a line 16
|
|
77
|
+
// times longer costs about 16 times as much scanned once, and far more
|
|
78
|
+
// (about 60 times, measured) scanned again with every read. The slowest
|
|
79
|
+
// short check against the fastest long one, so noise only helps a scan
|
|
80
|
+
// that is linear pass.
|
|
81
|
+
const timed = async (name: string, chars: number): Promise<number> => {
|
|
82
|
+
await put(name, `---\nsummary: "${'x'.repeat(chars)}"\n${RULE}---\n`);
|
|
83
|
+
const started = performance.now();
|
|
84
|
+
await svc.canReadBatch(workspaceId, ali, [`Open/${name}`]);
|
|
85
|
+
return performance.now() - started;
|
|
86
|
+
};
|
|
87
|
+
const short: number[] = [];
|
|
88
|
+
const long: number[] = [];
|
|
89
|
+
for (let i = 0; i < 3; i++) {
|
|
90
|
+
short.push(await timed(`short-${i}.md`, 2_000_000));
|
|
91
|
+
long.push(await timed(`long-${i}.md`, 32_000_000));
|
|
92
|
+
}
|
|
93
|
+
expect(Math.min(...long) / Math.max(...short)).toBeLessThan(32);
|
|
94
|
+
}, 60_000);
|
|
95
|
+
|
|
96
|
+
it('applies the fence rule as the whole-file scan does: CRLF, a BOM, padding around the fence', async () => {
|
|
97
|
+
await put('crlf.md', `---\r\n${RULE.replace(/\n/g, '\r\n')}---\r\nbody\r\n`);
|
|
98
|
+
await put('bom.md', `\uFEFF---\n${RULE}---\n`);
|
|
99
|
+
await put('padded.md', `${' '.repeat(5_000)}--- \n${RULE} ---\n`);
|
|
100
|
+
for (const name of ['crlf.md', 'bom.md', 'padded.md']) {
|
|
101
|
+
expect(await verdicts(name), name).toEqual({ ali: false, felix: true, single: false });
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('finds no rules where the whole-file scan finds none', async () => {
|
|
106
|
+
// A fenced block that is not at the top is body, not frontmatter.
|
|
107
|
+
await put('later.md', `# title\n---\n${RULE}---\n`);
|
|
108
|
+
// An opening fence never closed is no frontmatter either.
|
|
109
|
+
await put('unclosed.md', `---\n${RULE}`);
|
|
110
|
+
// A first line that is long, and no fence, ends the read at once.
|
|
111
|
+
await put('long-line.md', `${'x'.repeat(3_000_000)}\n---\n${RULE}---\n`);
|
|
112
|
+
await put('empty.md', '');
|
|
113
|
+
for (const name of ['later.md', 'unclosed.md', 'long-line.md', 'empty.md']) {
|
|
114
|
+
expect(await verdicts(name), name).toEqual({ ali: true, felix: true, single: true });
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
});
|