@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
package/src/shared/path-order.ts
CHANGED
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
2
|
+
* The TWO orderings of repository paths, and which consumer takes which:
|
|
3
|
+
*
|
|
4
|
+
* - {@link comparePathComponents} is the platform's own walk order — component
|
|
5
|
+
* by component, each by `localeCompare`, a shorter path before its
|
|
6
|
+
* extensions — exactly the order a directory walk that sorts each level's
|
|
7
|
+
* entries visits them in. A consumer that must agree with THAT walk on
|
|
8
|
+
* "first by path" (plugin discovery keeps the first of two folders sharing a
|
|
9
|
+
* slug; principal synthesis must pick the same one) sorts with it and
|
|
10
|
+
* nothing else.
|
|
11
|
+
* - {@link compareSearchPaths} is ripgrep's walk order (`--sort path`):
|
|
12
|
+
* component by component, each by its UTF-8 bytes. A `grep` answer is
|
|
13
|
+
* assembled from several ripgrep runs and a document pass, and it has to
|
|
14
|
+
* sort as ripgrep itself sorts, or the search could not tell which files a
|
|
15
|
+
* run has passed. The two orders differ where collation and bytes do
|
|
16
|
+
* (`B.md` sorts before `a.md` by bytes, after it by collation), which is why
|
|
17
|
+
* a grep answer and the explorer tree can list two siblings the other way
|
|
18
|
+
* round. Nothing but the search uses it.
|
|
19
|
+
*
|
|
20
|
+
* Both agree on the shape: a whole-string compare does not — it weighs the
|
|
21
|
+
* separator against the next character (`a-b/x` before `a/b/x`), a walk never
|
|
22
|
+
* does.
|
|
10
23
|
*
|
|
11
24
|
* TOTAL over distinct paths: two components the collation cannot tell apart
|
|
12
25
|
* (a precomposed `é` and its decomposed spelling) fall through to code-unit
|
|
@@ -31,3 +44,19 @@ function compareComponent(a: string, b: string): number {
|
|
|
31
44
|
if (order !== 0) return order;
|
|
32
45
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
33
46
|
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The order every `grep` search answers in — ripgrep's: path component by
|
|
50
|
+
* component, each compared by its UTF-8 bytes, so a folder's files come before
|
|
51
|
+
* a sibling file whose name extends the folder's. See the note above on how it
|
|
52
|
+
* differs from {@link comparePathComponents}, and why the search needs it.
|
|
53
|
+
*/
|
|
54
|
+
export function compareSearchPaths(a: string, b: string): number {
|
|
55
|
+
const as = a.split('/');
|
|
56
|
+
const bs = b.split('/');
|
|
57
|
+
for (let i = 0; i < Math.min(as.length, bs.length); i++) {
|
|
58
|
+
const c = Buffer.compare(Buffer.from(as[i]!, 'utf8'), Buffer.from(bs[i]!, 'utf8'));
|
|
59
|
+
if (c !== 0) return c;
|
|
60
|
+
}
|
|
61
|
+
return as.length - bs.length;
|
|
62
|
+
}
|
|
@@ -9,7 +9,7 @@ read:
|
|
|
9
9
|
# manifest. A starter plugin is for the whole team, so everyone may use it
|
|
10
10
|
# and admins change it. To narrow it, replace `everyone` under `read:` with a
|
|
11
11
|
# role from roles.yaml (as `role/<Name>`, so a group of the same name can
|
|
12
|
-
# never stand in for it), a group
|
|
12
|
+
# never stand in for it), a group as `group/<Name>`, or a person as
|
|
13
13
|
# `Name <email>`. Keep this block pure YAML; explanations go in `#` lines.
|
|
14
14
|
read:
|
|
15
15
|
- everyone
|
|
@@ -9,7 +9,7 @@ read:
|
|
|
9
9
|
# manifest. A starter plugin is for the whole team, so everyone may use it
|
|
10
10
|
# and admins change it. To narrow it, replace `everyone` under `read:` with a
|
|
11
11
|
# role from roles.yaml (as `role/<Name>`, so a group of the same name can
|
|
12
|
-
# never stand in for it), a group
|
|
12
|
+
# never stand in for it), a group as `group/<Name>`, or a person as
|
|
13
13
|
# `Name <email>`. Keep this block pure YAML; explanations go in `#` lines.
|
|
14
14
|
read:
|
|
15
15
|
- everyone
|
|
@@ -9,7 +9,7 @@ read:
|
|
|
9
9
|
# manifest. A starter plugin is for the whole team, so everyone may use it
|
|
10
10
|
# and admins change it. To narrow it, replace `everyone` under `read:` with a
|
|
11
11
|
# role from roles.yaml (as `role/<Name>`, so a group of the same name can
|
|
12
|
-
# never stand in for it), a group
|
|
12
|
+
# never stand in for it), a group as `group/<Name>`, or a person as
|
|
13
13
|
# `Name <email>`. Keep this block pure YAML; explanations go in `#` lines.
|
|
14
14
|
read:
|
|
15
15
|
- everyone
|
|
@@ -9,7 +9,7 @@ read:
|
|
|
9
9
|
# manifest. A starter plugin is for the whole team, so everyone may use it
|
|
10
10
|
# and admins change it. To narrow it, replace `everyone` under `read:` with a
|
|
11
11
|
# role from roles.yaml (as `role/<Name>`, so a group of the same name can
|
|
12
|
-
# never stand in for it), a group
|
|
12
|
+
# never stand in for it), a group as `group/<Name>`, or a person as
|
|
13
13
|
# `Name <email>`. Keep this block pure YAML; explanations go in `#` lines.
|
|
14
14
|
read:
|
|
15
15
|
- everyone
|
|
@@ -9,7 +9,7 @@ read:
|
|
|
9
9
|
# manifest. A starter plugin is for the whole team, so everyone may use it
|
|
10
10
|
# and admins change it. To narrow it, replace `everyone` under `read:` with a
|
|
11
11
|
# role from roles.yaml (as `role/<Name>`, so a group of the same name can
|
|
12
|
-
# never stand in for it), a group
|
|
12
|
+
# never stand in for it), a group as `group/<Name>`, or a person as
|
|
13
13
|
# `Name <email>`. Keep this block pure YAML; explanations go in `#` lines.
|
|
14
14
|
read:
|
|
15
15
|
- everyone
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { type IPatternMatcher, type IPatternSearch } from './pattern-matcher.contract.js';
|
|
2
|
-
/**
|
|
3
|
-
* The one place a CALLER'S regular expression is run (see
|
|
4
|
-
* {@link IPatternMatcher}).
|
|
5
|
-
*
|
|
6
|
-
* The pattern never runs on the request thread: the lines go to a worker, the
|
|
7
|
-
* worker answers which of them match, and a search whose matching outlives its
|
|
8
|
-
* budget has its worker terminated (which interrupts even a regex
|
|
9
|
-
* mid-backtrack) and is answered with {@link PatternTooExpensiveError}.
|
|
10
|
-
* Nothing partial is returned: a search that blew its budget answers the 400
|
|
11
|
-
* alone.
|
|
12
|
-
*
|
|
13
|
-
* The semantics are the engine's own — the worker compiles the same source with
|
|
14
|
-
* the same flags and calls `test` on each line — so a pattern within budget
|
|
15
|
-
* matches exactly the lines it always did.
|
|
16
|
-
*
|
|
17
|
-
* The pool is this instance's own (its mechanics are the shared
|
|
18
|
-
* {@link WorkerPool}; the regex worker below is this kind's): the composition
|
|
19
|
-
* root builds one per graph and closes it when the graph stops, so no worker
|
|
20
|
-
* outlives what it served.
|
|
21
|
-
*/
|
|
22
|
-
export interface WorkerPatternMatcherOptions {
|
|
23
|
-
/** Matching time one search may spend, summed over every file it searches. Default 5 s. */
|
|
24
|
-
budgetMs?: number;
|
|
25
|
-
/** Workers kept for searches at once; a search past this waits for one to free up. Default 4. */
|
|
26
|
-
poolMax?: number;
|
|
27
|
-
/**
|
|
28
|
-
* Searches that may wait for a worker at once. A waiting search holds the
|
|
29
|
-
* lines it was handed, so the queue is capped: past it a search is refused
|
|
30
|
-
* with a 503 rather than piling file contents up in memory. Default 16.
|
|
31
|
-
*/
|
|
32
|
-
maxWaiting?: number;
|
|
33
|
-
/**
|
|
34
|
-
* Longest pattern accepted. The pattern is compiled on the request thread
|
|
35
|
-
* (that is how a syntax error earns its 400 there), so its length is what
|
|
36
|
-
* bounds that. Default 1000.
|
|
37
|
-
*/
|
|
38
|
-
maxPatternLength?: number;
|
|
39
|
-
/**
|
|
40
|
-
* Characters handed to the worker in one message. Posting a message copies
|
|
41
|
-
* it on the request thread, so a large file goes over in pieces of about
|
|
42
|
-
* this size rather than in one copy that would stall every other request.
|
|
43
|
-
* A single line longer than this is not posted at all: it is written into
|
|
44
|
-
* shared memory this many characters at a time, yielding between pieces,
|
|
45
|
-
* and the worker reads it from there. Default 1 MB.
|
|
46
|
-
*/
|
|
47
|
-
batchChars?: number;
|
|
48
|
-
/**
|
|
49
|
-
* Lines handed to the worker in one message. Each line is an entry the
|
|
50
|
-
* copy has to walk whatever its length, so a file of empty lines is bounded
|
|
51
|
-
* by this, not by `batchChars`. Default 10,000.
|
|
52
|
-
*/
|
|
53
|
-
batchLines?: number;
|
|
54
|
-
}
|
|
55
|
-
export declare class WorkerPatternMatcher implements IPatternMatcher {
|
|
56
|
-
private readonly budgetMs;
|
|
57
|
-
private readonly maxPatternLength;
|
|
58
|
-
private readonly batchChars;
|
|
59
|
-
private readonly batchLines;
|
|
60
|
-
private readonly pool;
|
|
61
|
-
private readonly lease;
|
|
62
|
-
constructor(opts?: WorkerPatternMatcherOptions);
|
|
63
|
-
search(source: string, flags: string): IPatternSearch;
|
|
64
|
-
close(): Promise<void>;
|
|
65
|
-
/**
|
|
66
|
-
* Where the batch starting at `from` ends: about `batchChars` characters on,
|
|
67
|
-
* at most `batchLines` lines on, and never empty. A batch of one line past
|
|
68
|
-
* `batchChars` is handed over through shared memory instead (see
|
|
69
|
-
* {@link BoundedSearch.matchBatch}).
|
|
70
|
-
*/
|
|
71
|
-
private batchEnd;
|
|
72
|
-
}
|
|
73
|
-
//# sourceMappingURL=bounded-matcher.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bounded-matcher.d.ts","sourceRoot":"","sources":["../../../src/modules/workspace/bounded-matcher.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,cAAc,EAGpB,MAAM,+BAA+B,CAAC;AAEvC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,2BAA2B;IAC1C,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iGAAiG;IACjG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA4CD,qBAAa,oBAAqB,YAAW,eAAe;IAC1D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAa;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;gBAElB,IAAI,GAAE,2BAAgC;IAwBlD,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,cAAc;IAgBrD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB;;;;;OAKG;IACH,OAAO,CAAC,QAAQ;CAOjB"}
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
import { Worker } from 'node:worker_threads';
|
|
2
|
-
import { exchange, MAX_TIMER_MS, sizingOption, WorkerPool } from './worker-pool.js';
|
|
3
|
-
import { ToolError } from '../tool-helpers/index.js';
|
|
4
|
-
import { PatternTooExpensiveError, SearchBusyError, } from './pattern-matcher.contract.js';
|
|
5
|
-
/**
|
|
6
|
-
* Slack on top of the remaining budget before a batch is given up on: the
|
|
7
|
-
* budget counts only the worker's own matching time, so handing the lines over
|
|
8
|
-
* and the answer back must not be what trips it.
|
|
9
|
-
*/
|
|
10
|
-
const TRANSFER_GRACE_MS = 250;
|
|
11
|
-
/** The worker: compiles the pattern it is handed and answers the indices of the lines it matches. */
|
|
12
|
-
const WORKER_SOURCE = `
|
|
13
|
-
const { parentPort } = require('node:worker_threads');
|
|
14
|
-
const { performance } = require('node:perf_hooks');
|
|
15
|
-
let cached = { key: null, re: null };
|
|
16
|
-
parentPort.on('message', ({ source, flags, lines, shared, limit }) => {
|
|
17
|
-
try {
|
|
18
|
-
// One line too long to post, written into shared memory as raw UTF-16 code units.
|
|
19
|
-
if (shared) lines = [Buffer.from(shared).toString('utf16le')];
|
|
20
|
-
const key = flags + '/' + source;
|
|
21
|
-
if (cached.key !== key) cached = { key, re: new RegExp(source, flags) };
|
|
22
|
-
const re = cached.re;
|
|
23
|
-
const started = performance.now();
|
|
24
|
-
const hits = [];
|
|
25
|
-
for (let i = 0; i < lines.length && hits.length < limit; i++) {
|
|
26
|
-
re.lastIndex = 0;
|
|
27
|
-
if (re.test(lines[i])) hits.push(i);
|
|
28
|
-
}
|
|
29
|
-
parentPort.postMessage({ hits, elapsed: performance.now() - started });
|
|
30
|
-
} catch (err) {
|
|
31
|
-
parentPort.postMessage({ error: String(err && err.message ? err.message : err) });
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
`;
|
|
35
|
-
export class WorkerPatternMatcher {
|
|
36
|
-
budgetMs;
|
|
37
|
-
maxPatternLength;
|
|
38
|
-
batchChars;
|
|
39
|
-
batchLines;
|
|
40
|
-
pool;
|
|
41
|
-
lease;
|
|
42
|
-
constructor(opts = {}) {
|
|
43
|
-
this.budgetMs = option('budgetMs', opts.budgetMs ?? 5_000, 1, MAX_TIMER_MS - TRANSFER_GRACE_MS);
|
|
44
|
-
this.pool = new WorkerPool(() => new Worker(WORKER_SOURCE, { eval: true }), {
|
|
45
|
-
// A pool of none would leave every search waiting for a worker that never comes.
|
|
46
|
-
poolMax: option('poolMax', opts.poolMax ?? 4, 1),
|
|
47
|
-
// No queue at all is allowed: every search past the pool is refused busy.
|
|
48
|
-
maxWaiting: option('maxWaiting', opts.maxWaiting ?? 16, 0),
|
|
49
|
-
}, () => new SearchBusyError());
|
|
50
|
-
this.maxPatternLength = option('maxPatternLength', opts.maxPatternLength ?? 1_000, 1);
|
|
51
|
-
this.batchChars = option('batchChars', opts.batchChars ?? 1_000_000, 1);
|
|
52
|
-
this.batchLines = option('batchLines', opts.batchLines ?? 10_000, 1);
|
|
53
|
-
this.lease = {
|
|
54
|
-
acquire: () => this.pool.acquire(),
|
|
55
|
-
release: (w) => this.pool.release(w),
|
|
56
|
-
discard: (w) => this.pool.discard(w),
|
|
57
|
-
batchEnd: (lines, from) => this.batchEnd(lines, from),
|
|
58
|
-
batchChars: this.batchChars,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
search(source, flags) {
|
|
62
|
-
if (source.length > this.maxPatternLength) {
|
|
63
|
-
throw new ToolError(`Pattern too long: ${source.length} characters, and a search pattern may have at most ${this.maxPatternLength}.`, 400, { code: 'pattern_too_long' });
|
|
64
|
-
}
|
|
65
|
-
try {
|
|
66
|
-
new RegExp(source, flags);
|
|
67
|
-
}
|
|
68
|
-
catch (err) {
|
|
69
|
-
throw new ToolError(`Invalid regex: ${err.message}`, 400);
|
|
70
|
-
}
|
|
71
|
-
return new BoundedSearch(this.lease, source, flags, this.budgetMs);
|
|
72
|
-
}
|
|
73
|
-
close() {
|
|
74
|
-
return this.pool.close();
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Where the batch starting at `from` ends: about `batchChars` characters on,
|
|
78
|
-
* at most `batchLines` lines on, and never empty. A batch of one line past
|
|
79
|
-
* `batchChars` is handed over through shared memory instead (see
|
|
80
|
-
* {@link BoundedSearch.matchBatch}).
|
|
81
|
-
*/
|
|
82
|
-
batchEnd(lines, from) {
|
|
83
|
-
let to = from;
|
|
84
|
-
let chars = 0;
|
|
85
|
-
const last = Math.min(lines.length, from + this.batchLines);
|
|
86
|
-
while (to < last && (to === from || chars + lines[to].length <= this.batchChars))
|
|
87
|
-
chars += lines[to++].length;
|
|
88
|
-
return to;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
/** One search: a caller's pattern, matched off the event loop under one budget. */
|
|
92
|
-
class BoundedSearch {
|
|
93
|
-
pool;
|
|
94
|
-
source;
|
|
95
|
-
flags;
|
|
96
|
-
budgetMs;
|
|
97
|
-
spentMs = 0;
|
|
98
|
-
constructor(pool, source, flags, budgetMs) {
|
|
99
|
-
this.pool = pool;
|
|
100
|
-
this.source = source;
|
|
101
|
-
this.flags = flags;
|
|
102
|
-
this.budgetMs = budgetMs;
|
|
103
|
-
}
|
|
104
|
-
async matchingLines(lines, limit) {
|
|
105
|
-
if (limit <= 0 || lines.length === 0)
|
|
106
|
-
return [];
|
|
107
|
-
if (this.spentMs >= this.budgetMs)
|
|
108
|
-
throw new PatternTooExpensiveError(this.source, this.budgetMs);
|
|
109
|
-
const w = await this.pool.acquire();
|
|
110
|
-
const hits = [];
|
|
111
|
-
try {
|
|
112
|
-
for (let from = 0; from < lines.length && hits.length < limit;) {
|
|
113
|
-
const to = this.pool.batchEnd(lines, from);
|
|
114
|
-
for (const i of await this.matchBatch(w, lines.slice(from, to), limit - hits.length))
|
|
115
|
-
hits.push(from + i);
|
|
116
|
-
from = to;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
catch (err) {
|
|
120
|
-
// Terminating is what stops a regex mid-backtrack; the pool replaces it.
|
|
121
|
-
this.pool.discard(w);
|
|
122
|
-
throw err;
|
|
123
|
-
}
|
|
124
|
-
this.pool.release(w);
|
|
125
|
-
return hits;
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* One handoff to the worker, charged to this search's budget. No handoff
|
|
129
|
-
* copies more than `batchChars` characters on the request thread at once: a
|
|
130
|
-
* line longer than that goes over in shared memory, not in the message.
|
|
131
|
-
*/
|
|
132
|
-
async matchBatch(w, lines, limit) {
|
|
133
|
-
if (this.spentMs >= this.budgetMs)
|
|
134
|
-
throw new PatternTooExpensiveError(this.source, this.budgetMs);
|
|
135
|
-
const oversized = lines.length === 1 && lines[0].length > this.pool.batchChars;
|
|
136
|
-
const payload = oversized ? { shared: await shareLine(w, lines[0], this.pool.batchChars) } : { lines };
|
|
137
|
-
const remaining = this.budgetMs - this.spentMs;
|
|
138
|
-
const reply = await run(w, { source: this.source, flags: this.flags, ...payload, limit }, remaining + TRANSFER_GRACE_MS);
|
|
139
|
-
if (reply === 'timeout') {
|
|
140
|
-
this.spentMs = this.budgetMs;
|
|
141
|
-
throw new PatternTooExpensiveError(this.source, this.budgetMs);
|
|
142
|
-
}
|
|
143
|
-
if ('error' in reply)
|
|
144
|
-
throw new ToolError(`Invalid regex: ${reply.error}`, 400);
|
|
145
|
-
this.spentMs += reply.elapsed;
|
|
146
|
-
if (this.spentMs > this.budgetMs)
|
|
147
|
-
throw new PatternTooExpensiveError(this.source, this.budgetMs);
|
|
148
|
-
return reply.hits;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* A line as raw UTF-16 code units in shared memory, which a message hands the
|
|
153
|
-
* worker without copying. It is written `chunkChars` at a time with a turn of
|
|
154
|
-
* the event loop between, so a line of any length never holds the request
|
|
155
|
-
* thread for longer than one chunk. Raw code units keep the line exact, lone
|
|
156
|
-
* surrogates included.
|
|
157
|
-
*
|
|
158
|
-
* The worker it is meant for can be terminated by `close()` during those turns,
|
|
159
|
-
* before {@link run} listens for its exit; a message posted to it then would
|
|
160
|
-
* never be answered. So its exit is watched here too: writing stops and the
|
|
161
|
-
* search is answered busy, as one terminated under `run` is.
|
|
162
|
-
*/
|
|
163
|
-
async function shareLine(w, line, chunkChars) {
|
|
164
|
-
let exited = false;
|
|
165
|
-
const onExit = () => {
|
|
166
|
-
exited = true;
|
|
167
|
-
};
|
|
168
|
-
w.once('exit', onExit);
|
|
169
|
-
try {
|
|
170
|
-
const shared = new SharedArrayBuffer(line.length * 2);
|
|
171
|
-
const bytes = Buffer.from(shared);
|
|
172
|
-
for (let at = 0; at < line.length && !exited; at += chunkChars) {
|
|
173
|
-
if (at > 0)
|
|
174
|
-
await new Promise((resolve) => setImmediate(resolve));
|
|
175
|
-
bytes.write(line.slice(at, at + chunkChars), at * 2, 'utf16le');
|
|
176
|
-
}
|
|
177
|
-
if (exited)
|
|
178
|
-
throw new SearchBusyError();
|
|
179
|
-
return shared;
|
|
180
|
-
}
|
|
181
|
-
finally {
|
|
182
|
-
w.off('exit', onExit);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
async function run(w, message, timeoutMs) {
|
|
186
|
-
const outcome = await exchange(w, message, timeoutMs);
|
|
187
|
-
if ('reply' in outcome)
|
|
188
|
-
return outcome.reply;
|
|
189
|
-
if ('timeout' in outcome)
|
|
190
|
-
return 'timeout';
|
|
191
|
-
if ('error' in outcome)
|
|
192
|
-
throw outcome.error;
|
|
193
|
-
// Terminated under the search by `close()`: answered as busy, not left hanging until the timer.
|
|
194
|
-
throw new SearchBusyError();
|
|
195
|
-
}
|
|
196
|
-
/** A sizing option as given, refused at construction unless it is an integer from `min` to `max`. */
|
|
197
|
-
function option(name, value, min, max = Number.MAX_SAFE_INTEGER) {
|
|
198
|
-
return sizingOption('WorkerPatternMatcher', name, value, min, max);
|
|
199
|
-
}
|
|
200
|
-
//# sourceMappingURL=bounded-matcher.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bounded-matcher.js","sourceRoot":"","sources":["../../../src/modules/workspace/bounded-matcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
import { Worker } from 'node:worker_threads';
|
|
2
|
-
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
-
import { WorkerPatternMatcher, type WorkerPatternMatcherOptions } from '../bounded-matcher.js';
|
|
4
|
-
import { PatternTooExpensiveError } from '../pattern-matcher.contract.js';
|
|
5
|
-
|
|
6
|
-
/** The QA finding's pattern, and a 40-character line it backtracks on for ever. */
|
|
7
|
-
const CATASTROPHIC = '^(a+)+$';
|
|
8
|
-
const LONG_LINE = `${'a'.repeat(39)}!`;
|
|
9
|
-
/** The ticket's per-search budget (Razvan, 2026-10-15). */
|
|
10
|
-
const FIVE_SECONDS = 5_000;
|
|
11
|
-
|
|
12
|
-
let matchers: WorkerPatternMatcher[] = [];
|
|
13
|
-
/** A matcher of the test's own, closed after it — the defaults unless a test says otherwise. */
|
|
14
|
-
function matcher(opts?: WorkerPatternMatcherOptions): WorkerPatternMatcher {
|
|
15
|
-
const m = new WorkerPatternMatcher(opts);
|
|
16
|
-
matchers.push(m);
|
|
17
|
-
return m;
|
|
18
|
-
}
|
|
19
|
-
afterEach(async () => {
|
|
20
|
-
await Promise.all(matchers.map((m) => m.close()));
|
|
21
|
-
matchers = [];
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* The longest the event loop went without running a timer while `work` ran.
|
|
26
|
-
* A pattern run on the loop would show the whole match time here.
|
|
27
|
-
*/
|
|
28
|
-
async function longestStall(work: () => Promise<unknown>): Promise<number> {
|
|
29
|
-
let last = Date.now();
|
|
30
|
-
let worst = 0;
|
|
31
|
-
const tick = setInterval(() => {
|
|
32
|
-
const now = Date.now();
|
|
33
|
-
worst = Math.max(worst, now - last);
|
|
34
|
-
last = now;
|
|
35
|
-
}, 10);
|
|
36
|
-
try {
|
|
37
|
-
await work().catch(() => {});
|
|
38
|
-
} finally {
|
|
39
|
-
clearInterval(tick);
|
|
40
|
-
}
|
|
41
|
-
return Math.max(worst, Date.now() - last);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
describe('WorkerPatternMatcher', () => {
|
|
45
|
-
it('stops a catastrophic pattern over a 40-character line with the worded 400 in its 5 s budget, and never stalls the loop', async () => {
|
|
46
|
-
expect(LONG_LINE).toHaveLength(40);
|
|
47
|
-
const started = Date.now();
|
|
48
|
-
let caught: unknown;
|
|
49
|
-
const stall = await longestStall(async () => {
|
|
50
|
-
try {
|
|
51
|
-
await matcher().search(CATASTROPHIC, '').matchingLines([LONG_LINE], 200);
|
|
52
|
-
} catch (err) {
|
|
53
|
-
caught = err;
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
const took = Date.now() - started;
|
|
57
|
-
expect(caught).toBeInstanceOf(PatternTooExpensiveError);
|
|
58
|
-
expect((caught as PatternTooExpensiveError).status).toBe(400);
|
|
59
|
-
expect((caught as Error).message).toMatch(/^Pattern too expensive: /);
|
|
60
|
-
expect((caught as Error).message).toContain('simpler pattern');
|
|
61
|
-
expect((caught as Error).message).toContain("this search's 5 s budget");
|
|
62
|
-
expect(took).toBeGreaterThanOrEqual(FIVE_SECONDS - 100);
|
|
63
|
-
expect(took).toBeLessThan(FIVE_SECONDS + 2_000);
|
|
64
|
-
expect(stall).toBeLessThan(500);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('answers ordinary patterns with exactly the lines RegExp#test matches', async () => {
|
|
68
|
-
const m = matcher();
|
|
69
|
-
const lines = ['hello world', 'Getting started', 'aaa', 'line 42', '', 'foo|bar', 'bar baz', LONG_LINE];
|
|
70
|
-
for (const [source, flags] of [
|
|
71
|
-
['world', ''],
|
|
72
|
-
['^aaa$', ''],
|
|
73
|
-
['getting started', 'i'],
|
|
74
|
-
['[0-9]+', ''],
|
|
75
|
-
['foo|baz', ''],
|
|
76
|
-
['^$', ''],
|
|
77
|
-
['a+!$', ''],
|
|
78
|
-
] as const) {
|
|
79
|
-
const re = new RegExp(source, flags);
|
|
80
|
-
const expected = lines.flatMap((l, i) => (re.test(l) ? [i] : []));
|
|
81
|
-
expect(await m.search(source, flags).matchingLines(lines, 1000), `/${source}/${flags}`).toEqual(expected);
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('returns only the first `limit` matches', async () => {
|
|
86
|
-
expect(await matcher().search('x', '').matchingLines(['x', 'y', 'x', 'x'], 2)).toEqual([0, 2]);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('refuses a sizing that could never serve a search when it is built, not when a search hangs on it', () => {
|
|
90
|
-
expect(() => new WorkerPatternMatcher({ poolMax: 0 })).toThrow(/poolMax must be an integer from 1 /);
|
|
91
|
-
// Past the longest delay setTimeout honours, the timer would fire after 1 ms and stop every search.
|
|
92
|
-
expect(() => new WorkerPatternMatcher({ budgetMs: 2_147_483_647 })).toThrow(/budgetMs must be an integer from 1 to 2147483397/);
|
|
93
|
-
expect(() => new WorkerPatternMatcher({ poolMax: 1.5 })).toThrow(RangeError);
|
|
94
|
-
expect(() => new WorkerPatternMatcher({ budgetMs: 0 })).toThrow(/budgetMs/);
|
|
95
|
-
expect(() => new WorkerPatternMatcher({ maxWaiting: -1 })).toThrow(/maxWaiting/);
|
|
96
|
-
expect(() => new WorkerPatternMatcher({ batchChars: Number.NaN })).toThrow(/batchChars/);
|
|
97
|
-
expect(() => new WorkerPatternMatcher({ batchLines: 0 })).toThrow(/batchLines/);
|
|
98
|
-
expect(() => matcher({ maxWaiting: 0 })).not.toThrow();
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('serves an ordinary search under the largest budget the timer can hold', async () => {
|
|
102
|
-
expect(await matcher({ budgetMs: 2_147_483_647 - 250 }).search('b', '').matchingLines(['a', 'b'], 10)).toEqual([1]);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('refuses invalid syntax with the 400 grep always gave', () => {
|
|
106
|
-
expect(() => matcher().search('(', '')).toThrow(/^Invalid regex: /);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('refuses a pattern too long to compile on the request thread, and accepts one at the limit', () => {
|
|
110
|
-
const m = matcher({ maxPatternLength: 10 });
|
|
111
|
-
expect(() => m.search('a'.repeat(11), '')).toThrow(/^Pattern too long: /);
|
|
112
|
-
expect(() => m.search('a'.repeat(10), '')).not.toThrow();
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('hands a large file over in batches and answers the same line numbers, up to `limit`', async () => {
|
|
116
|
-
const m = matcher({ batchChars: 100 });
|
|
117
|
-
const filler = 'x'.repeat(9);
|
|
118
|
-
const lines = Array.from({ length: 250 }, (_, i) => (i % 7 === 0 ? `hit ${i}` : filler));
|
|
119
|
-
const expected = lines.flatMap((l, i) => (l.startsWith('hit') ? [i] : []));
|
|
120
|
-
expect(expected.at(-1)! * filler.length).toBeGreaterThan(200);
|
|
121
|
-
expect(await m.search('^hit', '').matchingLines(lines, 10_000)).toEqual(expected);
|
|
122
|
-
expect(await m.search('^hit', '').matchingLines(lines, 3)).toEqual(expected.slice(0, 3));
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('caps a batch by its line count too, so a file of empty lines never goes over in one message', async () => {
|
|
126
|
-
const m = matcher({ batchLines: 100 });
|
|
127
|
-
const lines = Array.from({ length: 1_050 }, (_, i) => (i % 97 === 0 ? 'hit' : ''));
|
|
128
|
-
const expected = lines.flatMap((l, i) => (l === 'hit' ? [i] : []));
|
|
129
|
-
const posted = vi.spyOn(Worker.prototype, 'postMessage');
|
|
130
|
-
try {
|
|
131
|
-
expect(await m.search('^hit$', '').matchingLines(lines, 10_000)).toEqual(expected);
|
|
132
|
-
expect(await m.search('^$', '').matchingLines(lines, 10_000)).toHaveLength(lines.length - expected.length);
|
|
133
|
-
const postedLines = posted.mock.calls.map(([msg]) => ((msg as { lines?: string[] }).lines ?? []).length);
|
|
134
|
-
expect(postedLines.filter((n) => n > 0)).toHaveLength(2 * 11);
|
|
135
|
-
expect(Math.max(...postedLines)).toBeLessThanOrEqual(100);
|
|
136
|
-
} finally {
|
|
137
|
-
posted.mockRestore();
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('hands a line longer than one batch over in shared memory, never in a message, and matches it exactly', async () => {
|
|
142
|
-
const m = matcher({ batchChars: 100 });
|
|
143
|
-
// A lone surrogate included: the line must reach the worker code unit for code unit.
|
|
144
|
-
const long = `${'x'.repeat(1_000)}\uD800 tail ${'y'.repeat(500)}`;
|
|
145
|
-
const lines = ['short', long, 'z'.repeat(400), 'end tail'];
|
|
146
|
-
const posted = vi.spyOn(Worker.prototype, 'postMessage');
|
|
147
|
-
try {
|
|
148
|
-
for (const pattern of ['tail', '\\uD800 tail', '^x+\\uD800', 'y{500}$', '^z+$', 'nowhere']) {
|
|
149
|
-
const re = new RegExp(pattern);
|
|
150
|
-
const expected = lines.flatMap((l, i) => (re.test(l) ? [i] : []));
|
|
151
|
-
expect(await m.search(pattern, '').matchingLines(lines, 10_000)).toEqual(expected);
|
|
152
|
-
}
|
|
153
|
-
const postedChars = posted.mock.calls.map(([msg]) =>
|
|
154
|
-
((msg as { lines?: string[] }).lines ?? []).reduce((n, l) => n + l.length, 0),
|
|
155
|
-
);
|
|
156
|
-
expect(postedChars.length).toBeGreaterThan(0);
|
|
157
|
-
expect(Math.max(...postedChars)).toBeLessThanOrEqual(100);
|
|
158
|
-
} finally {
|
|
159
|
-
posted.mockRestore();
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it('refuses a search with 503 once the queue for a worker is full, rather than holding its lines', async () => {
|
|
164
|
-
const m = matcher({ budgetMs: 500, poolMax: 2, maxWaiting: 2 });
|
|
165
|
-
// Every worker held by a pattern that runs out its budget, the queue filled behind them.
|
|
166
|
-
const holding = Promise.allSettled(Array.from({ length: 2 }, () => m.search(CATASTROPHIC, '').matchingLines([LONG_LINE], 1)));
|
|
167
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
168
|
-
const queued = Array.from({ length: 2 }, () => m.search('b', '').matchingLines(['b'], 1));
|
|
169
|
-
await expect(m.search('b', '').matchingLines(['b'], 1)).rejects.toMatchObject({
|
|
170
|
-
status: 503,
|
|
171
|
-
details: { code: 'search_busy' },
|
|
172
|
-
});
|
|
173
|
-
// The queued searches are served once the workers come free.
|
|
174
|
-
expect(await Promise.all(queued)).toEqual(queued.map(() => [0]));
|
|
175
|
-
expect((await holding).every((r) => r.status === 'rejected')).toBe(true);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it('stops two expensive searches at once, and a search started meanwhile still answers', async () => {
|
|
179
|
-
const m = matcher({ budgetMs: 500 });
|
|
180
|
-
const results = await Promise.allSettled([
|
|
181
|
-
m.search(CATASTROPHIC, '').matchingLines([LONG_LINE], 10),
|
|
182
|
-
m.search('(a|aa)+$', '').matchingLines([`${'a'.repeat(60)}!`], 10),
|
|
183
|
-
m.search('aaa', '').matchingLines(['xaaay'], 10),
|
|
184
|
-
]);
|
|
185
|
-
expect(results[0]).toMatchObject({ status: 'rejected', reason: expect.any(PatternTooExpensiveError) });
|
|
186
|
-
expect(results[1]).toMatchObject({ status: 'rejected', reason: expect.any(PatternTooExpensiveError) });
|
|
187
|
-
expect(results[2]).toEqual({ status: 'fulfilled', value: [0] });
|
|
188
|
-
// The terminated workers were replaced: the pool still serves.
|
|
189
|
-
expect(await m.search('b', '').matchingLines(['a', 'b'], 10)).toEqual([1]);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it('ends its workers on close: a search running then, and one after, is answered busy', async () => {
|
|
193
|
-
const m = matcher();
|
|
194
|
-
const running = m.search(CATASTROPHIC, '').matchingLines([LONG_LINE], 1);
|
|
195
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
196
|
-
const started = Date.now();
|
|
197
|
-
await m.close();
|
|
198
|
-
await expect(running).rejects.toMatchObject({ status: 503, details: { code: 'search_busy' } });
|
|
199
|
-
expect(Date.now() - started).toBeLessThan(1_000);
|
|
200
|
-
await expect(m.search('b', '').matchingLines(['b'], 1)).rejects.toMatchObject({ status: 503 });
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
it('answers busy at once when closed while a long line is still being written to shared memory', async () => {
|
|
204
|
-
// A line of a million characters written ten at a time: many turns of the loop to close in.
|
|
205
|
-
const m = matcher({ budgetMs: 3_000, batchChars: 10 });
|
|
206
|
-
const running = m.search('nowhere', '').matchingLines(['a'.repeat(1_000_000)], 1);
|
|
207
|
-
await new Promise((r) => setTimeout(r, 20));
|
|
208
|
-
const started = Date.now();
|
|
209
|
-
await m.close();
|
|
210
|
-
await expect(running).rejects.toMatchObject({ status: 503, details: { code: 'search_busy' } });
|
|
211
|
-
expect(Date.now() - started).toBeLessThan(1_000);
|
|
212
|
-
});
|
|
213
|
-
});
|