@bevel-software/platform-core-backend 0.1.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/LICENSE +202 -0
- package/dist/assets.d.ts +5 -0
- package/dist/assets.d.ts.map +1 -0
- package/dist/assets.js +23 -0
- package/dist/assets.js.map +1 -0
- package/dist/core/core-ports.d.ts +83 -0
- package/dist/core/core-ports.d.ts.map +1 -0
- package/dist/core/core-ports.js +12 -0
- package/dist/core/core-ports.js.map +1 -0
- package/dist/core/create-core-server.d.ts +62 -0
- package/dist/core/create-core-server.d.ts.map +1 -0
- package/dist/core/create-core-server.js +204 -0
- package/dist/core/create-core-server.js.map +1 -0
- package/dist/core/create-core-services.d.ts +95 -0
- package/dist/core/create-core-services.d.ts.map +1 -0
- package/dist/core/create-core-services.js +352 -0
- package/dist/core/create-core-services.js.map +1 -0
- package/dist/core-config.d.ts +119 -0
- package/dist/core-config.d.ts.map +1 -0
- package/dist/core-config.js +240 -0
- package/dist/core-config.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/access/access-control.interface.d.ts +348 -0
- package/dist/modules/access/access-control.interface.d.ts.map +1 -0
- package/dist/modules/access/access-control.interface.js +2 -0
- package/dist/modules/access/access-control.interface.js.map +1 -0
- package/dist/modules/access/access-control.service.d.ts +323 -0
- package/dist/modules/access/access-control.service.d.ts.map +1 -0
- package/dist/modules/access/access-control.service.js +1431 -0
- package/dist/modules/access/access-control.service.js.map +1 -0
- package/dist/modules/access/access-errors.d.ts +34 -0
- package/dist/modules/access/access-errors.d.ts.map +1 -0
- package/dist/modules/access/access-errors.js +40 -0
- package/dist/modules/access/access-errors.js.map +1 -0
- package/dist/modules/access/access-mutation.service.d.ts +176 -0
- package/dist/modules/access/access-mutation.service.d.ts.map +1 -0
- package/dist/modules/access/access-mutation.service.js +307 -0
- package/dist/modules/access/access-mutation.service.js.map +1 -0
- package/dist/modules/access/access-splice.d.ts +98 -0
- package/dist/modules/access/access-splice.d.ts.map +1 -0
- package/dist/modules/access/access-splice.js +346 -0
- package/dist/modules/access/access-splice.js.map +1 -0
- package/dist/modules/access/access.routes.d.ts +10 -0
- package/dist/modules/access/access.routes.d.ts.map +1 -0
- package/dist/modules/access/access.routes.js +757 -0
- package/dist/modules/access/access.routes.js.map +1 -0
- package/dist/modules/access/creator-access.d.ts +113 -0
- package/dist/modules/access/creator-access.d.ts.map +1 -0
- package/dist/modules/access/creator-access.js +212 -0
- package/dist/modules/access/creator-access.js.map +1 -0
- package/dist/modules/access/kb-read-filter.d.ts +41 -0
- package/dist/modules/access/kb-read-filter.d.ts.map +1 -0
- package/dist/modules/access/kb-read-filter.js +60 -0
- package/dist/modules/access/kb-read-filter.js.map +1 -0
- package/dist/modules/access/roles-admin.service.d.ts +240 -0
- package/dist/modules/access/roles-admin.service.d.ts.map +1 -0
- package/dist/modules/access/roles-admin.service.js +630 -0
- package/dist/modules/access/roles-admin.service.js.map +1 -0
- package/dist/modules/access/roles-edit.d.ts +91 -0
- package/dist/modules/access/roles-edit.d.ts.map +1 -0
- package/dist/modules/access/roles-edit.js +232 -0
- package/dist/modules/access/roles-edit.js.map +1 -0
- package/dist/modules/access/roles-yaml-guard.d.ts +56 -0
- package/dist/modules/access/roles-yaml-guard.d.ts.map +1 -0
- package/dist/modules/access/roles-yaml-guard.js +79 -0
- package/dist/modules/access/roles-yaml-guard.js.map +1 -0
- package/dist/modules/admin/admin-access.routes.d.ts +18 -0
- package/dist/modules/admin/admin-access.routes.d.ts.map +1 -0
- package/dist/modules/admin/admin-access.routes.js +26 -0
- package/dist/modules/admin/admin-access.routes.js.map +1 -0
- package/dist/modules/admin/admin-access.service.d.ts +24 -0
- package/dist/modules/admin/admin-access.service.d.ts.map +1 -0
- package/dist/modules/admin/admin-access.service.js +41 -0
- package/dist/modules/admin/admin-access.service.js.map +1 -0
- package/dist/modules/admin/admin.interface.d.ts +11 -0
- package/dist/modules/admin/admin.interface.d.ts.map +1 -0
- package/dist/modules/admin/admin.interface.js +2 -0
- package/dist/modules/admin/admin.interface.js.map +1 -0
- package/dist/modules/auth/account-erasure.service.d.ts +80 -0
- package/dist/modules/auth/account-erasure.service.d.ts.map +1 -0
- package/dist/modules/auth/account-erasure.service.js +91 -0
- package/dist/modules/auth/account-erasure.service.js.map +1 -0
- package/dist/modules/auth/auth.middleware.d.ts +23 -0
- package/dist/modules/auth/auth.middleware.d.ts.map +1 -0
- package/dist/modules/auth/auth.middleware.js +68 -0
- package/dist/modules/auth/auth.middleware.js.map +1 -0
- package/dist/modules/auth/auth.routes.d.ts +25 -0
- package/dist/modules/auth/auth.routes.d.ts.map +1 -0
- package/dist/modules/auth/auth.routes.js +72 -0
- package/dist/modules/auth/auth.routes.js.map +1 -0
- package/dist/modules/auth/auth.service.d.ts +76 -0
- package/dist/modules/auth/auth.service.d.ts.map +1 -0
- package/dist/modules/auth/auth.service.js +168 -0
- package/dist/modules/auth/auth.service.js.map +1 -0
- package/dist/modules/code-mode/code-mode-names.d.ts +16 -0
- package/dist/modules/code-mode/code-mode-names.d.ts.map +1 -0
- package/dist/modules/code-mode/code-mode-names.js +30 -0
- package/dist/modules/code-mode/code-mode-names.js.map +1 -0
- package/dist/modules/code-mode/code-mode.tool.d.ts +13 -0
- package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -0
- package/dist/modules/code-mode/code-mode.tool.js +112 -0
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -0
- package/dist/modules/code-mode/index.d.ts +3 -0
- package/dist/modules/code-mode/index.d.ts.map +1 -0
- package/dist/modules/code-mode/index.js +3 -0
- package/dist/modules/code-mode/index.js.map +1 -0
- package/dist/modules/database/connection.d.ts +6 -0
- package/dist/modules/database/connection.d.ts.map +1 -0
- package/dist/modules/database/connection.js +12 -0
- package/dist/modules/database/connection.js.map +1 -0
- package/dist/modules/database/core-schema.d.ts +2273 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -0
- package/dist/modules/database/core-schema.js +413 -0
- package/dist/modules/database/core-schema.js.map +1 -0
- package/dist/modules/database/migrate.d.ts +10 -0
- package/dist/modules/database/migrate.d.ts.map +1 -0
- package/dist/modules/database/migrate.js +40 -0
- package/dist/modules/database/migrate.js.map +1 -0
- package/dist/modules/database/schema.d.ts +12 -0
- package/dist/modules/database/schema.d.ts.map +1 -0
- package/dist/modules/database/schema.js +12 -0
- package/dist/modules/database/schema.js.map +1 -0
- package/dist/modules/diff/diff-paths.d.ts +2 -0
- package/dist/modules/diff/diff-paths.d.ts.map +1 -0
- package/dist/modules/diff/diff-paths.js +9 -0
- package/dist/modules/diff/diff-paths.js.map +1 -0
- package/dist/modules/diff/diff.config.d.ts +8 -0
- package/dist/modules/diff/diff.config.d.ts.map +1 -0
- package/dist/modules/diff/diff.config.js +13 -0
- package/dist/modules/diff/diff.config.js.map +1 -0
- package/dist/modules/diff/diff.interface.d.ts +73 -0
- package/dist/modules/diff/diff.interface.d.ts.map +1 -0
- package/dist/modules/diff/diff.interface.js +2 -0
- package/dist/modules/diff/diff.interface.js.map +1 -0
- package/dist/modules/diff/diff.routes.d.ts +35 -0
- package/dist/modules/diff/diff.routes.d.ts.map +1 -0
- package/dist/modules/diff/diff.routes.js +228 -0
- package/dist/modules/diff/diff.routes.js.map +1 -0
- package/dist/modules/diff/diff.service.d.ts +51 -0
- package/dist/modules/diff/diff.service.d.ts.map +1 -0
- package/dist/modules/diff/diff.service.js +489 -0
- package/dist/modules/diff/diff.service.js.map +1 -0
- package/dist/modules/diff/line-diff.d.ts +16 -0
- package/dist/modules/diff/line-diff.d.ts.map +1 -0
- package/dist/modules/diff/line-diff.js +67 -0
- package/dist/modules/diff/line-diff.js.map +1 -0
- package/dist/modules/mcp/mcp-auth.middleware.d.ts +27 -0
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -0
- package/dist/modules/mcp/mcp-auth.middleware.js +116 -0
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -0
- package/dist/modules/mcp/mcp-session-store.d.ts +74 -0
- package/dist/modules/mcp/mcp-session-store.d.ts.map +1 -0
- package/dist/modules/mcp/mcp-session-store.js +131 -0
- package/dist/modules/mcp/mcp-session-store.js.map +1 -0
- package/dist/modules/mcp/mcp.routes.d.ts +24 -0
- package/dist/modules/mcp/mcp.routes.d.ts.map +1 -0
- package/dist/modules/mcp/mcp.routes.js +229 -0
- package/dist/modules/mcp/mcp.routes.js.map +1 -0
- package/dist/modules/mcp/mcp.service.d.ts +198 -0
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -0
- package/dist/modules/mcp/mcp.service.js +840 -0
- package/dist/modules/mcp/mcp.service.js.map +1 -0
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +76 -0
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -0
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +291 -0
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -0
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts +18 -0
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -0
- package/dist/modules/mcp/oauth/oauth-consent.routes.js +52 -0
- package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -0
- package/dist/modules/mcp/oauth/oauth-state.d.ts +32 -0
- package/dist/modules/mcp/oauth/oauth-state.d.ts.map +1 -0
- package/dist/modules/mcp/oauth/oauth-state.js +34 -0
- package/dist/modules/mcp/oauth/oauth-state.js.map +1 -0
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +92 -0
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -0
- package/dist/modules/secrets-vault/db-secrets-vault.service.js +728 -0
- package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -0
- package/dist/modules/secrets-vault/index.d.ts +7 -0
- package/dist/modules/secrets-vault/index.d.ts.map +1 -0
- package/dist/modules/secrets-vault/index.js +6 -0
- package/dist/modules/secrets-vault/index.js.map +1 -0
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.d.ts +63 -0
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.d.ts.map +1 -0
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js +269 -0
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js.map +1 -0
- package/dist/modules/secrets-vault/secrets-variable-loader.d.ts +46 -0
- package/dist/modules/secrets-vault/secrets-variable-loader.d.ts.map +1 -0
- package/dist/modules/secrets-vault/secrets-variable-loader.js +87 -0
- package/dist/modules/secrets-vault/secrets-variable-loader.js.map +1 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +210 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.js +90 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -0
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +30 -0
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -0
- package/dist/modules/secrets-vault/secrets-vault.routes.js +490 -0
- package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -0
- package/dist/modules/skills/index.d.ts +5 -0
- package/dist/modules/skills/index.d.ts.map +1 -0
- package/dist/modules/skills/index.js +4 -0
- package/dist/modules/skills/index.js.map +1 -0
- package/dist/modules/skills/skills.contract.d.ts +58 -0
- package/dist/modules/skills/skills.contract.d.ts.map +1 -0
- package/dist/modules/skills/skills.contract.js +10 -0
- package/dist/modules/skills/skills.contract.js.map +1 -0
- package/dist/modules/skills/skills.routes.d.ts +15 -0
- package/dist/modules/skills/skills.routes.d.ts.map +1 -0
- package/dist/modules/skills/skills.routes.js +34 -0
- package/dist/modules/skills/skills.routes.js.map +1 -0
- package/dist/modules/skills/skills.service.d.ts +21 -0
- package/dist/modules/skills/skills.service.d.ts.map +1 -0
- package/dist/modules/skills/skills.service.js +202 -0
- package/dist/modules/skills/skills.service.js.map +1 -0
- package/dist/modules/skills/skills.tools.d.ts +15 -0
- package/dist/modules/skills/skills.tools.d.ts.map +1 -0
- package/dist/modules/skills/skills.tools.js +96 -0
- package/dist/modules/skills/skills.tools.js.map +1 -0
- package/dist/modules/tool-auth/external-api-key.errors.d.ts +14 -0
- package/dist/modules/tool-auth/external-api-key.errors.d.ts.map +1 -0
- package/dist/modules/tool-auth/external-api-key.errors.js +23 -0
- package/dist/modules/tool-auth/external-api-key.errors.js.map +1 -0
- package/dist/modules/tool-auth/external-api-key.interface.d.ts +90 -0
- package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -0
- package/dist/modules/tool-auth/external-api-key.interface.js +2 -0
- package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -0
- package/dist/modules/tool-auth/external-api-key.service.d.ts +25 -0
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -0
- package/dist/modules/tool-auth/external-api-key.service.js +164 -0
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -0
- package/dist/modules/tool-auth/internal-token.service.d.ts +62 -0
- package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -0
- package/dist/modules/tool-auth/internal-token.service.js +85 -0
- package/dist/modules/tool-auth/internal-token.service.js.map +1 -0
- package/dist/modules/tool-auth/llm-usage-meter.d.ts +16 -0
- package/dist/modules/tool-auth/llm-usage-meter.d.ts.map +1 -0
- package/dist/modules/tool-auth/llm-usage-meter.js +6 -0
- package/dist/modules/tool-auth/llm-usage-meter.js.map +1 -0
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts +101 -0
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -0
- package/dist/modules/tool-auth/tool-auth.middleware.js +166 -0
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -0
- package/dist/modules/tool-helpers/index.d.ts +19 -0
- package/dist/modules/tool-helpers/index.d.ts.map +1 -0
- package/dist/modules/tool-helpers/index.js +18 -0
- package/dist/modules/tool-helpers/index.js.map +1 -0
- package/dist/modules/tool-helpers/tool-context.d.ts +35 -0
- package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -0
- package/dist/modules/tool-helpers/tool-context.js +66 -0
- package/dist/modules/tool-helpers/tool-context.js.map +1 -0
- package/dist/modules/tool-helpers/tool-def.d.ts +33 -0
- package/dist/modules/tool-helpers/tool-def.d.ts.map +1 -0
- package/dist/modules/tool-helpers/tool-def.js +57 -0
- package/dist/modules/tool-helpers/tool-def.js.map +1 -0
- package/dist/modules/tool-helpers/tool-handler.d.ts +19 -0
- package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -0
- package/dist/modules/tool-helpers/tool-handler.js +84 -0
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -0
- package/dist/modules/tool-helpers/tool.contract.d.ts +64 -0
- package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -0
- package/dist/modules/tool-helpers/tool.contract.js +24 -0
- package/dist/modules/tool-helpers/tool.contract.js.map +1 -0
- package/dist/modules/tool-helpers/validate-token.d.ts +31 -0
- package/dist/modules/tool-helpers/validate-token.d.ts.map +1 -0
- package/dist/modules/tool-helpers/validate-token.js +20 -0
- package/dist/modules/tool-helpers/validate-token.js.map +1 -0
- package/dist/modules/tool-manuals/index.d.ts +5 -0
- package/dist/modules/tool-manuals/index.d.ts.map +1 -0
- package/dist/modules/tool-manuals/index.js +5 -0
- package/dist/modules/tool-manuals/index.js.map +1 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +182 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.js +19 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.js.map +1 -0
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +24 -0
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -0
- package/dist/modules/tool-manuals/tool-manuals.routes.js +102 -0
- package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -0
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +108 -0
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -0
- package/dist/modules/tool-manuals/tool-manuals.service.js +710 -0
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -0
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +35 -0
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -0
- package/dist/modules/tool-manuals/tool-manuals.tools.js +157 -0
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -0
- package/dist/modules/tool-registry/manual.routes.d.ts +6 -0
- package/dist/modules/tool-registry/manual.routes.d.ts.map +1 -0
- package/dist/modules/tool-registry/manual.routes.js +76 -0
- package/dist/modules/tool-registry/manual.routes.js.map +1 -0
- package/dist/modules/tool-registry/tool-registry.d.ts +23 -0
- package/dist/modules/tool-registry/tool-registry.d.ts.map +1 -0
- package/dist/modules/tool-registry/tool-registry.js +46 -0
- package/dist/modules/tool-registry/tool-registry.js.map +1 -0
- package/dist/modules/tool-registry/tool.contract.d.ts +48 -0
- package/dist/modules/tool-registry/tool.contract.d.ts.map +1 -0
- package/dist/modules/tool-registry/tool.contract.js +2 -0
- package/dist/modules/tool-registry/tool.contract.js.map +1 -0
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts +14 -0
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -0
- package/dist/modules/workflow/agent-tools/workflow.tools.js +437 -0
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -0
- package/dist/modules/workflow/event-bus.d.ts +107 -0
- package/dist/modules/workflow/event-bus.d.ts.map +1 -0
- package/dist/modules/workflow/event-bus.js +224 -0
- package/dist/modules/workflow/event-bus.js.map +1 -0
- package/dist/modules/workflow/events.routes.d.ts +80 -0
- package/dist/modules/workflow/events.routes.d.ts.map +1 -0
- package/dist/modules/workflow/events.routes.js +216 -0
- package/dist/modules/workflow/events.routes.js.map +1 -0
- package/dist/modules/workflow/file-change-notifier.d.ts +38 -0
- package/dist/modules/workflow/file-change-notifier.d.ts.map +1 -0
- package/dist/modules/workflow/file-change-notifier.js +22 -0
- package/dist/modules/workflow/file-change-notifier.js.map +1 -0
- package/dist/modules/workflow/file-lock.service.d.ts +57 -0
- package/dist/modules/workflow/file-lock.service.d.ts.map +1 -0
- package/dist/modules/workflow/file-lock.service.js +202 -0
- package/dist/modules/workflow/file-lock.service.js.map +1 -0
- package/dist/modules/workflow/git/branch-name.d.ts +10 -0
- package/dist/modules/workflow/git/branch-name.d.ts.map +1 -0
- package/dist/modules/workflow/git/branch-name.js +76 -0
- package/dist/modules/workflow/git/branch-name.js.map +1 -0
- package/dist/modules/workflow/git/clone-config.d.ts +61 -0
- package/dist/modules/workflow/git/clone-config.d.ts.map +1 -0
- package/dist/modules/workflow/git/clone-config.js +69 -0
- package/dist/modules/workflow/git/clone-config.js.map +1 -0
- package/dist/modules/workflow/git/git-version.d.ts +34 -0
- package/dist/modules/workflow/git/git-version.d.ts.map +1 -0
- package/dist/modules/workflow/git/git-version.js +63 -0
- package/dist/modules/workflow/git/git-version.js.map +1 -0
- package/dist/modules/workflow/git/git.service.d.ts +427 -0
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -0
- package/dist/modules/workflow/git/git.service.js +1987 -0
- package/dist/modules/workflow/git/git.service.js.map +1 -0
- package/dist/modules/workflow/git/mutex.d.ts +11 -0
- package/dist/modules/workflow/git/mutex.d.ts.map +1 -0
- package/dist/modules/workflow/git/mutex.js +23 -0
- package/dist/modules/workflow/git/mutex.js.map +1 -0
- package/dist/modules/workflow/git/pull-request.service.d.ts +124 -0
- package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -0
- package/dist/modules/workflow/git/pull-request.service.js +328 -0
- package/dist/modules/workflow/git/pull-request.service.js.map +1 -0
- package/dist/modules/workflow/locking-filesystem.d.ts +133 -0
- package/dist/modules/workflow/locking-filesystem.d.ts.map +1 -0
- package/dist/modules/workflow/locking-filesystem.js +400 -0
- package/dist/modules/workflow/locking-filesystem.js.map +1 -0
- package/dist/modules/workflow/pending-commits.service.d.ts +221 -0
- package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -0
- package/dist/modules/workflow/pending-commits.service.js +364 -0
- package/dist/modules/workflow/pending-commits.service.js.map +1 -0
- package/dist/modules/workflow/pending-commits.worker.d.ts +148 -0
- package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -0
- package/dist/modules/workflow/pending-commits.worker.js +245 -0
- package/dist/modules/workflow/pending-commits.worker.js.map +1 -0
- package/dist/modules/workflow/read-only-filesystem.d.ts +24 -0
- package/dist/modules/workflow/read-only-filesystem.d.ts.map +1 -0
- package/dist/modules/workflow/read-only-filesystem.js +39 -0
- package/dist/modules/workflow/read-only-filesystem.js.map +1 -0
- package/dist/modules/workflow/recovery-bot.d.ts +31 -0
- package/dist/modules/workflow/recovery-bot.d.ts.map +1 -0
- package/dist/modules/workflow/recovery-bot.js +64 -0
- package/dist/modules/workflow/recovery-bot.js.map +1 -0
- package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts +137 -0
- package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts.map +1 -0
- package/dist/modules/workflow/review-workflow/review-workflow.interface.js +2 -0
- package/dist/modules/workflow/review-workflow/review-workflow.interface.js.map +1 -0
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts +26 -0
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -0
- package/dist/modules/workflow/review-workflow/review-workflow.service.js +636 -0
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -0
- package/dist/modules/workflow/sanitize-error.d.ts +29 -0
- package/dist/modules/workflow/sanitize-error.d.ts.map +1 -0
- package/dist/modules/workflow/sanitize-error.js +60 -0
- package/dist/modules/workflow/sanitize-error.js.map +1 -0
- package/dist/modules/workflow/session-ontology.policy.d.ts +52 -0
- package/dist/modules/workflow/session-ontology.policy.d.ts.map +1 -0
- package/dist/modules/workflow/session-ontology.policy.js +62 -0
- package/dist/modules/workflow/session-ontology.policy.js.map +1 -0
- package/dist/modules/workflow/session-ontology.service.d.ts +100 -0
- package/dist/modules/workflow/session-ontology.service.d.ts.map +1 -0
- package/dist/modules/workflow/session-ontology.service.js +143 -0
- package/dist/modules/workflow/session-ontology.service.js.map +1 -0
- package/dist/modules/workflow/workflow-hooks.d.ts +87 -0
- package/dist/modules/workflow/workflow-hooks.d.ts.map +1 -0
- package/dist/modules/workflow/workflow-hooks.js +35 -0
- package/dist/modules/workflow/workflow-hooks.js.map +1 -0
- package/dist/modules/workflow/workflow.errors.d.ts +171 -0
- package/dist/modules/workflow/workflow.errors.d.ts.map +1 -0
- package/dist/modules/workflow/workflow.errors.js +260 -0
- package/dist/modules/workflow/workflow.errors.js.map +1 -0
- package/dist/modules/workflow/workflow.routes.d.ts +26 -0
- package/dist/modules/workflow/workflow.routes.d.ts.map +1 -0
- package/dist/modules/workflow/workflow.routes.js +804 -0
- package/dist/modules/workflow/workflow.routes.js.map +1 -0
- package/dist/modules/workflow/workflow.service.d.ts +385 -0
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -0
- package/dist/modules/workflow/workflow.service.js +1155 -0
- package/dist/modules/workflow/workflow.service.js.map +1 -0
- package/dist/modules/workspace/bevel-ignore.d.ts +25 -0
- package/dist/modules/workspace/bevel-ignore.d.ts.map +1 -0
- package/dist/modules/workspace/bevel-ignore.js +57 -0
- package/dist/modules/workspace/bevel-ignore.js.map +1 -0
- package/dist/modules/workspace/kb-seed.interface.d.ts +36 -0
- package/dist/modules/workspace/kb-seed.interface.d.ts.map +1 -0
- package/dist/modules/workspace/kb-seed.interface.js +2 -0
- package/dist/modules/workspace/kb-seed.interface.js.map +1 -0
- package/dist/modules/workspace/kb-seed.service.d.ts +70 -0
- package/dist/modules/workspace/kb-seed.service.d.ts.map +1 -0
- package/dist/modules/workspace/kb-seed.service.js +325 -0
- package/dist/modules/workspace/kb-seed.service.js.map +1 -0
- package/dist/modules/workspace/routine-write-policy.d.ts +49 -0
- package/dist/modules/workspace/routine-write-policy.d.ts.map +1 -0
- package/dist/modules/workspace/routine-write-policy.js +46 -0
- package/dist/modules/workspace/routine-write-policy.js.map +1 -0
- package/dist/modules/workspace/session-ontology.gate.d.ts +112 -0
- package/dist/modules/workspace/session-ontology.gate.d.ts.map +1 -0
- package/dist/modules/workspace/session-ontology.gate.js +161 -0
- package/dist/modules/workspace/session-ontology.gate.js.map +1 -0
- package/dist/modules/workspace/session-sink.d.ts +25 -0
- package/dist/modules/workspace/session-sink.d.ts.map +1 -0
- package/dist/modules/workspace/session-sink.js +8 -0
- package/dist/modules/workspace/session-sink.js.map +1 -0
- package/dist/modules/workspace/spill-store.d.ts +24 -0
- package/dist/modules/workspace/spill-store.d.ts.map +1 -0
- package/dist/modules/workspace/spill-store.js +87 -0
- package/dist/modules/workspace/spill-store.js.map +1 -0
- package/dist/modules/workspace/workspace.routes.d.ts +25 -0
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -0
- package/dist/modules/workspace/workspace.routes.js +1046 -0
- package/dist/modules/workspace/workspace.routes.js.map +1 -0
- package/dist/modules/workspace/workspace.service.d.ts +339 -0
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -0
- package/dist/modules/workspace/workspace.service.js +1221 -0
- package/dist/modules/workspace/workspace.service.js.map +1 -0
- package/dist/modules/workspace/workspace.tools.d.ts +19 -0
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -0
- package/dist/modules/workspace/workspace.tools.js +767 -0
- package/dist/modules/workspace/workspace.tools.js.map +1 -0
- package/dist/shared/frontmatter-id.d.ts +35 -0
- package/dist/shared/frontmatter-id.d.ts.map +1 -0
- package/dist/shared/frontmatter-id.js +68 -0
- package/dist/shared/frontmatter-id.js.map +1 -0
- package/dist/shared/fs-walk.d.ts +9 -0
- package/dist/shared/fs-walk.d.ts.map +1 -0
- package/dist/shared/fs-walk.js +34 -0
- package/dist/shared/fs-walk.js.map +1 -0
- package/dist/shared/hash-email.d.ts +11 -0
- package/dist/shared/hash-email.d.ts.map +1 -0
- package/dist/shared/hash-email.js +14 -0
- package/dist/shared/hash-email.js.map +1 -0
- package/dist/shared/kb-layout.d.ts +38 -0
- package/dist/shared/kb-layout.d.ts.map +1 -0
- package/dist/shared/kb-layout.js +102 -0
- package/dist/shared/kb-layout.js.map +1 -0
- package/dist/shared/kb-layout.test.d.ts +2 -0
- package/dist/shared/kb-layout.test.d.ts.map +1 -0
- package/dist/shared/kb-layout.test.js +67 -0
- package/dist/shared/kb-layout.test.js.map +1 -0
- package/dist/shared/ssrf.d.ts +20 -0
- package/dist/shared/ssrf.d.ts.map +1 -0
- package/dist/shared/ssrf.js +89 -0
- package/dist/shared/ssrf.js.map +1 -0
- package/dist/shared/token-crypto.d.ts +16 -0
- package/dist/shared/token-crypto.d.ts.map +1 -0
- package/dist/shared/token-crypto.js +49 -0
- package/dist/shared/token-crypto.js.map +1 -0
- package/dist/shared/ttl-cache.d.ts +16 -0
- package/dist/shared/ttl-cache.d.ts.map +1 -0
- package/dist/shared/ttl-cache.js +27 -0
- package/dist/shared/ttl-cache.js.map +1 -0
- package/dist/shared/utcp-namespace.d.ts +46 -0
- package/dist/shared/utcp-namespace.d.ts.map +1 -0
- package/dist/shared/utcp-namespace.js +82 -0
- package/dist/shared/utcp-namespace.js.map +1 -0
- package/dist/version.d.ts +17 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +19 -0
- package/dist/version.js.map +1 -0
- package/kb-template/.bevelignore +21 -0
- package/kb-template/Agents/.gitkeep +0 -0
- package/kb-template/Agents/README.md +38 -0
- package/kb-template/CLAUDE.md +419 -0
- package/kb-template/Data/.gitkeep +0 -0
- package/kb-template/Data/README.md +39 -0
- package/kb-template/KnowledgeBase/.gitkeep +0 -0
- package/kb-template/KnowledgeBase/README.md +37 -0
- package/kb-template/Pipelines/.gitkeep +0 -0
- package/kb-template/Pipelines/README.md +38 -0
- package/kb-template/Skills/.gitkeep +0 -0
- package/kb-template/Skills/README.md +32 -0
- package/kb-template/Tools/.gitkeep +0 -0
- package/kb-template/Tools/README.md +42 -0
- package/kb-template/access.md +36 -0
- package/migrations/0000_core_init.sql +243 -0
- package/migrations/meta/0000_snapshot.json +1480 -0
- package/migrations/meta/_journal.json +13 -0
- package/package.json +80 -0
- package/src/assets.ts +25 -0
- package/src/core/core-ports.ts +91 -0
- package/src/core/create-core-server.ts +338 -0
- package/src/core/create-core-services.ts +501 -0
- package/src/core-config.ts +257 -0
- package/src/index.ts +69 -0
- package/src/modules/access/__tests__/access-control.atref-batch.test.ts +98 -0
- package/src/modules/access/__tests__/access-control.read-batch.test.ts +132 -0
- package/src/modules/access/__tests__/access-control.service.test.ts +1011 -0
- package/src/modules/access/__tests__/access-mutation.service.test.ts +439 -0
- package/src/modules/access/__tests__/access-splice.test.ts +247 -0
- package/src/modules/access/__tests__/access.routes.revoke.test.ts +407 -0
- package/src/modules/access/__tests__/creator-access.test.ts +217 -0
- package/src/modules/access/__tests__/grant-sources.test.ts +204 -0
- package/src/modules/access/__tests__/kb-read-filter.test.ts +70 -0
- package/src/modules/access/__tests__/roles-admin.service.test.ts +469 -0
- package/src/modules/access/__tests__/roles-edit.test.ts +138 -0
- package/src/modules/access/__tests__/roles-yaml-guard.test.ts +70 -0
- package/src/modules/access/__tests__/roles.routes.test.ts +234 -0
- package/src/modules/access/access-control.interface.ts +383 -0
- package/src/modules/access/access-control.service.ts +1794 -0
- package/src/modules/access/access-errors.ts +60 -0
- package/src/modules/access/access-mutation.service.ts +369 -0
- package/src/modules/access/access-splice.ts +413 -0
- package/src/modules/access/access.routes.ts +894 -0
- package/src/modules/access/creator-access.ts +270 -0
- package/src/modules/access/kb-read-filter.ts +76 -0
- package/src/modules/access/roles-admin.service.ts +741 -0
- package/src/modules/access/roles-edit.ts +248 -0
- package/src/modules/access/roles-yaml-guard.ts +89 -0
- package/src/modules/admin/admin-access.routes.ts +29 -0
- package/src/modules/admin/admin-access.service.ts +38 -0
- package/src/modules/admin/admin.interface.ts +10 -0
- package/src/modules/auth/account-erasure.service.ts +183 -0
- package/src/modules/auth/auth.middleware.ts +78 -0
- package/src/modules/auth/auth.routes.ts +97 -0
- package/src/modules/auth/auth.service.ts +199 -0
- package/src/modules/code-mode/code-mode-names.ts +38 -0
- package/src/modules/code-mode/code-mode.tool.ts +117 -0
- package/src/modules/code-mode/index.ts +6 -0
- package/src/modules/database/connection.ts +15 -0
- package/src/modules/database/core-schema.ts +431 -0
- package/src/modules/database/migrate.ts +43 -0
- package/src/modules/database/schema.ts +11 -0
- package/src/modules/diff/__tests__/diff.routes.read-gate.test.ts +87 -0
- package/src/modules/diff/__tests__/diff.routes.rejectPathsLocked.test.ts +150 -0
- package/src/modules/diff/__tests__/diff.service.seed-atomicity.test.ts +184 -0
- package/src/modules/diff/__tests__/diff.service.test.ts +202 -0
- package/src/modules/diff/diff-paths.ts +9 -0
- package/src/modules/diff/diff.config.ts +12 -0
- package/src/modules/diff/diff.interface.ts +78 -0
- package/src/modules/diff/diff.routes.ts +277 -0
- package/src/modules/diff/diff.service.ts +508 -0
- package/src/modules/diff/line-diff.ts +79 -0
- package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +287 -0
- package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +249 -0
- package/src/modules/mcp/__tests__/mcp-proxy-helpers.test.ts +253 -0
- package/src/modules/mcp/__tests__/mcp-session-store.test.ts +151 -0
- package/src/modules/mcp/__tests__/mcp.e2e.test.ts +150 -0
- package/src/modules/mcp/__tests__/mcp.routes.delete.test.ts +92 -0
- package/src/modules/mcp/__tests__/mcp.service.test.ts +414 -0
- package/src/modules/mcp/mcp-auth.middleware.ts +126 -0
- package/src/modules/mcp/mcp-session-store.ts +162 -0
- package/src/modules/mcp/mcp.routes.ts +263 -0
- package/src/modules/mcp/mcp.service.ts +973 -0
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +401 -0
- package/src/modules/mcp/oauth/oauth-consent.routes.ts +60 -0
- package/src/modules/mcp/oauth/oauth-state.ts +61 -0
- package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +277 -0
- package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +206 -0
- package/src/modules/secrets-vault/__tests__/mcp-oauth-discovery.service.test.ts +178 -0
- package/src/modules/secrets-vault/__tests__/scopes-covered.test.ts +101 -0
- package/src/modules/secrets-vault/__tests__/secrets-variable-loader.test.ts +54 -0
- package/src/modules/secrets-vault/__tests__/tool-owner-gate.route.test.ts +143 -0
- package/src/modules/secrets-vault/db-secrets-vault.service.ts +792 -0
- package/src/modules/secrets-vault/index.ts +25 -0
- package/src/modules/secrets-vault/mcp-oauth-discovery.service.ts +289 -0
- package/src/modules/secrets-vault/secrets-variable-loader.ts +104 -0
- package/src/modules/secrets-vault/secrets-vault.contract.ts +270 -0
- package/src/modules/secrets-vault/secrets-vault.routes.ts +526 -0
- package/src/modules/skills/__tests__/skills.service.test.ts +176 -0
- package/src/modules/skills/index.ts +10 -0
- package/src/modules/skills/skills.contract.ts +54 -0
- package/src/modules/skills/skills.routes.ts +38 -0
- package/src/modules/skills/skills.service.ts +245 -0
- package/src/modules/skills/skills.tools.ts +121 -0
- package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +358 -0
- package/src/modules/tool-auth/__tests__/internal-token.service.test.ts +79 -0
- package/src/modules/tool-auth/__tests__/manual-auth.middleware.test.ts +88 -0
- package/src/modules/tool-auth/external-api-key.errors.ts +25 -0
- package/src/modules/tool-auth/external-api-key.interface.ts +99 -0
- package/src/modules/tool-auth/external-api-key.service.ts +209 -0
- package/src/modules/tool-auth/internal-token.service.ts +124 -0
- package/src/modules/tool-auth/llm-usage-meter.ts +19 -0
- package/src/modules/tool-auth/tool-auth.middleware.ts +220 -0
- package/src/modules/tool-helpers/__tests__/phase0-loopback-spike.test.ts +129 -0
- package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +95 -0
- package/src/modules/tool-helpers/__tests__/tools-surface.test.ts +146 -0
- package/src/modules/tool-helpers/__tests__/validate-token.test.ts +75 -0
- package/src/modules/tool-helpers/index.ts +18 -0
- package/src/modules/tool-helpers/tool-context.ts +98 -0
- package/src/modules/tool-helpers/tool-def.ts +73 -0
- package/src/modules/tool-helpers/tool-handler.ts +91 -0
- package/src/modules/tool-helpers/tool.contract.ts +85 -0
- package/src/modules/tool-helpers/validate-token.ts +50 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +200 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +499 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +156 -0
- package/src/modules/tool-manuals/index.ts +12 -0
- package/src/modules/tool-manuals/tool-manuals.contract.ts +183 -0
- package/src/modules/tool-manuals/tool-manuals.routes.ts +110 -0
- package/src/modules/tool-manuals/tool-manuals.service.ts +781 -0
- package/src/modules/tool-manuals/tool-manuals.tools.ts +202 -0
- package/src/modules/tool-registry/__tests__/tool-registry.test.ts +64 -0
- package/src/modules/tool-registry/manual.routes.ts +87 -0
- package/src/modules/tool-registry/tool-registry.ts +55 -0
- package/src/modules/tool-registry/tool.contract.ts +52 -0
- package/src/modules/workflow/__tests__/event-bus.test.ts +322 -0
- package/src/modules/workflow/__tests__/file-change-notifier.test.ts +51 -0
- package/src/modules/workflow/__tests__/format-affected-owners.test.ts +67 -0
- package/src/modules/workflow/__tests__/locking-filesystem.test.ts +445 -0
- package/src/modules/workflow/__tests__/pending-commits.canonical.test.ts +36 -0
- package/src/modules/workflow/__tests__/pending-commits.worker.test.ts +243 -0
- package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +308 -0
- package/src/modules/workflow/__tests__/sanitize-error.test.ts +67 -0
- package/src/modules/workflow/__tests__/session-ontology.policy.test.ts +62 -0
- package/src/modules/workflow/__tests__/session-ontology.service.test.ts +200 -0
- package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +232 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +380 -0
- package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +340 -0
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +192 -0
- package/src/modules/workflow/agent-tools/workflow.tools.ts +502 -0
- package/src/modules/workflow/event-bus.ts +262 -0
- package/src/modules/workflow/events.routes.ts +266 -0
- package/src/modules/workflow/file-change-notifier.ts +54 -0
- package/src/modules/workflow/file-lock.service.ts +269 -0
- package/src/modules/workflow/git/__tests__/branch-name.test.ts +86 -0
- package/src/modules/workflow/git/__tests__/branchAuthor.test.ts +80 -0
- package/src/modules/workflow/git/__tests__/clone-config.test.ts +15 -0
- package/src/modules/workflow/git/__tests__/git-test-helpers.ts +47 -0
- package/src/modules/workflow/git/__tests__/git-version.failure.test.ts +24 -0
- package/src/modules/workflow/git/__tests__/git-version.test.ts +49 -0
- package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +389 -0
- package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +124 -0
- package/src/modules/workflow/git/__tests__/git.service.commitChanges.test.ts +105 -0
- package/src/modules/workflow/git/__tests__/git.service.commitFile.test.ts +211 -0
- package/src/modules/workflow/git/__tests__/git.service.createBranch.test.ts +154 -0
- package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +320 -0
- package/src/modules/workflow/git/__tests__/git.service.diffFileAtCommit.test.ts +202 -0
- package/src/modules/workflow/git/__tests__/git.service.diffFileBetweenBranches.test.ts +453 -0
- package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +198 -0
- package/src/modules/workflow/git/__tests__/git.service.listTrackedFiles.test.ts +125 -0
- package/src/modules/workflow/git/__tests__/git.service.logForFile.test.ts +219 -0
- package/src/modules/workflow/git/__tests__/git.service.mergeChangeRequest.test.ts +145 -0
- package/src/modules/workflow/git/__tests__/git.service.pendingChanges.test.ts +240 -0
- package/src/modules/workflow/git/__tests__/git.service.postMergePull.test.ts +272 -0
- package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +232 -0
- package/src/modules/workflow/git/__tests__/git.service.readFileAtRef.test.ts +101 -0
- package/src/modules/workflow/git/__tests__/git.service.status.test.ts +127 -0
- package/src/modules/workflow/git/__tests__/mutex.test.ts +78 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +225 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-cancel.test.ts +101 -0
- package/src/modules/workflow/git/branch-name.ts +70 -0
- package/src/modules/workflow/git/clone-config.ts +71 -0
- package/src/modules/workflow/git/git-version.ts +70 -0
- package/src/modules/workflow/git/git.service.ts +2248 -0
- package/src/modules/workflow/git/mutex.ts +22 -0
- package/src/modules/workflow/git/pull-request.service.ts +447 -0
- package/src/modules/workflow/locking-filesystem.ts +489 -0
- package/src/modules/workflow/pending-commits.service.ts +444 -0
- package/src/modules/workflow/pending-commits.worker.ts +361 -0
- package/src/modules/workflow/read-only-filesystem.ts +54 -0
- package/src/modules/workflow/recovery-bot.ts +73 -0
- package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +560 -0
- package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +176 -0
- package/src/modules/workflow/review-workflow/__tests__/merge-gate.test.ts +258 -0
- package/src/modules/workflow/review-workflow/review-workflow.interface.ts +199 -0
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +806 -0
- package/src/modules/workflow/sanitize-error.ts +62 -0
- package/src/modules/workflow/session-ontology.policy.ts +70 -0
- package/src/modules/workflow/session-ontology.service.ts +178 -0
- package/src/modules/workflow/workflow-hooks.ts +101 -0
- package/src/modules/workflow/workflow.errors.ts +285 -0
- package/src/modules/workflow/workflow.routes.ts +858 -0
- package/src/modules/workflow/workflow.service.ts +1555 -0
- package/src/modules/workspace/__tests__/kb-seed.service.test.ts +213 -0
- package/src/modules/workspace/__tests__/routine-write-policy.test.ts +68 -0
- package/src/modules/workspace/__tests__/session-ontology.gate.test.ts +237 -0
- package/src/modules/workspace/__tests__/spill-store.test.ts +43 -0
- package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +233 -0
- package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +176 -0
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +303 -0
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +276 -0
- package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +120 -0
- package/src/modules/workspace/__tests__/workspace.service.test.ts +565 -0
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +504 -0
- package/src/modules/workspace/bevel-ignore.ts +62 -0
- package/src/modules/workspace/kb-seed.interface.ts +36 -0
- package/src/modules/workspace/kb-seed.service.ts +361 -0
- package/src/modules/workspace/routine-write-policy.ts +79 -0
- package/src/modules/workspace/session-ontology.gate.ts +189 -0
- package/src/modules/workspace/session-sink.ts +25 -0
- package/src/modules/workspace/spill-store.ts +87 -0
- package/src/modules/workspace/workspace.routes.ts +1114 -0
- package/src/modules/workspace/workspace.service.ts +1343 -0
- package/src/modules/workspace/workspace.tools.ts +885 -0
- package/src/shared/__tests__/frontmatter-id.test.ts +88 -0
- package/src/shared/__tests__/fs-walk.test.ts +42 -0
- package/src/shared/__tests__/ssrf.test.ts +53 -0
- package/src/shared/__tests__/utcp-namespace.test.ts +19 -0
- package/src/shared/frontmatter-id.ts +72 -0
- package/src/shared/fs-walk.ts +30 -0
- package/src/shared/hash-email.ts +14 -0
- package/src/shared/kb-layout.test.ts +88 -0
- package/src/shared/kb-layout.ts +101 -0
- package/src/shared/ssrf.ts +78 -0
- package/src/shared/token-crypto.ts +54 -0
- package/src/shared/ttl-cache.ts +27 -0
- package/src/shared/utcp-namespace.ts +88 -0
- package/src/version.ts +19 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import type { AuthUser } from '@bevel-software/platform-shared';
|
|
3
|
+
import type { ISystemNoticeSink } from '../pending-commits.worker.js';
|
|
4
|
+
import {
|
|
5
|
+
N_RECOVERY,
|
|
6
|
+
N_TRANSIENT,
|
|
7
|
+
PendingCommitsWorker,
|
|
8
|
+
type RecoveryAgentRunner,
|
|
9
|
+
type WorkflowCommitDriver,
|
|
10
|
+
type WorkspaceProvider,
|
|
11
|
+
} from '../pending-commits.worker.js';
|
|
12
|
+
import type {
|
|
13
|
+
PendingCommit,
|
|
14
|
+
PendingCommitsService,
|
|
15
|
+
} from '../pending-commits.service.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Unit tests for the queue-draining worker. The service + workflow layers
|
|
19
|
+
* have their own tests; here we focus on the *decision tree* the worker
|
|
20
|
+
* applies per claimed row:
|
|
21
|
+
*
|
|
22
|
+
* - Success → markSucceeded, nothing else.
|
|
23
|
+
* - Failure inside transient budget → markTransientFailure, no recovery agent.
|
|
24
|
+
* - Failure outside transient budget but inside recovery budget → spawn agent.
|
|
25
|
+
* - Failure outside recovery budget → mark needs_attention + send feedback notice.
|
|
26
|
+
*
|
|
27
|
+
* We never actually start the polling loop (`start()`) — `drainOnce()` is
|
|
28
|
+
* the seam the tests step through. That avoids fake-timer choreography
|
|
29
|
+
* and keeps the test fast.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const RECOVERY_BOT = { id: 'recovery-bot-id', email: 'recovery-bot@bevel.local', name: 'Recovery Bot' };
|
|
33
|
+
|
|
34
|
+
function makeRow(overrides: Partial<PendingCommit> = {}): PendingCommit {
|
|
35
|
+
return {
|
|
36
|
+
id: overrides.id ?? 'row-1',
|
|
37
|
+
workspaceId: overrides.workspaceId ?? 'ws-1',
|
|
38
|
+
branch: overrides.branch ?? 'feat/x',
|
|
39
|
+
path: overrides.path ?? 'Foo.md',
|
|
40
|
+
authorEmail: overrides.authorEmail ?? 'alice@example.com',
|
|
41
|
+
authorName: overrides.authorName ?? 'Alice',
|
|
42
|
+
queuedAt: overrides.queuedAt ?? new Date('2026-01-01T00:00:00Z'),
|
|
43
|
+
status: overrides.status ?? 'running',
|
|
44
|
+
attempts: overrides.attempts ?? 0,
|
|
45
|
+
recoveryAgentRuns: overrides.recoveryAgentRuns ?? 0,
|
|
46
|
+
lastAttemptedAt: overrides.lastAttemptedAt ?? null,
|
|
47
|
+
lastError: overrides.lastError ?? null,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function makeService(): PendingCommitsService {
|
|
52
|
+
return {
|
|
53
|
+
enqueue: vi.fn().mockResolvedValue(undefined),
|
|
54
|
+
claimNext: vi.fn().mockResolvedValue(null),
|
|
55
|
+
markSucceeded: vi.fn().mockResolvedValue(undefined),
|
|
56
|
+
markTransientFailure: vi.fn().mockResolvedValue(undefined),
|
|
57
|
+
markRecoveryStarted: vi.fn().mockResolvedValue(undefined),
|
|
58
|
+
markNeedsAttention: vi.fn().mockResolvedValue(undefined),
|
|
59
|
+
listNeedsAttention: vi.fn().mockResolvedValue([]),
|
|
60
|
+
countPending: vi.fn().mockResolvedValue(0),
|
|
61
|
+
startupReconcile: vi.fn().mockResolvedValue(undefined),
|
|
62
|
+
} as unknown as PendingCommitsService;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function makeWorkspaces(): WorkspaceProvider {
|
|
66
|
+
return {
|
|
67
|
+
knownWorkspaces: () => [{ id: 'ws-1', branch: 'feat/x' }],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function makeFeedback(): ISystemNoticeSink {
|
|
72
|
+
return { send: vi.fn().mockResolvedValue(undefined) } as unknown as ISystemNoticeSink;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
describe('PendingCommitsWorker.drainOnce', () => {
|
|
76
|
+
let service: PendingCommitsService;
|
|
77
|
+
let workflow: WorkflowCommitDriver & {
|
|
78
|
+
runPendingCommit: ReturnType<typeof vi.fn>;
|
|
79
|
+
};
|
|
80
|
+
let recoveryAgent: RecoveryAgentRunner & { run: ReturnType<typeof vi.fn> };
|
|
81
|
+
let feedback: ISystemNoticeSink & { send: ReturnType<typeof vi.fn> };
|
|
82
|
+
let worker: PendingCommitsWorker;
|
|
83
|
+
|
|
84
|
+
beforeEach(() => {
|
|
85
|
+
service = makeService();
|
|
86
|
+
workflow = { runPendingCommit: vi.fn() } as never;
|
|
87
|
+
recoveryAgent = { run: vi.fn() } as never;
|
|
88
|
+
feedback = makeFeedback() as never;
|
|
89
|
+
worker = new PendingCommitsWorker({
|
|
90
|
+
service,
|
|
91
|
+
workflow,
|
|
92
|
+
recoveryAgent,
|
|
93
|
+
feedback,
|
|
94
|
+
workspaces: makeWorkspaces(),
|
|
95
|
+
recoveryBot: RECOVERY_BOT,
|
|
96
|
+
now: () => new Date('2026-01-01T00:01:00Z'),
|
|
97
|
+
});
|
|
98
|
+
// drainOnce checks `this.running` against the per-workspace iterator;
|
|
99
|
+
// start() sets it true. We don't want the polling loop, just the
|
|
100
|
+
// single-pass drain, so flip the flag manually.
|
|
101
|
+
(worker as unknown as { running: boolean }).running = true;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('happy path: claims row, runs commit, marks succeeded — no other branches taken', async () => {
|
|
105
|
+
const row = makeRow();
|
|
106
|
+
(service.claimNext as ReturnType<typeof vi.fn>).mockResolvedValueOnce(row);
|
|
107
|
+
workflow.runPendingCommit.mockResolvedValueOnce(undefined);
|
|
108
|
+
|
|
109
|
+
await worker.drainOnce();
|
|
110
|
+
|
|
111
|
+
expect(service.claimNext).toHaveBeenCalledWith('ws-1', expect.any(Date));
|
|
112
|
+
expect(workflow.runPendingCommit).toHaveBeenCalledWith(
|
|
113
|
+
'ws-1',
|
|
114
|
+
'feat/x',
|
|
115
|
+
'Foo.md',
|
|
116
|
+
expect.objectContaining<Partial<AuthUser>>({ email: 'alice@example.com', name: 'Alice' }),
|
|
117
|
+
);
|
|
118
|
+
expect(service.markSucceeded).toHaveBeenCalledWith('row-1');
|
|
119
|
+
expect(service.markTransientFailure).not.toHaveBeenCalled();
|
|
120
|
+
expect(service.markRecoveryStarted).not.toHaveBeenCalled();
|
|
121
|
+
expect(service.markNeedsAttention).not.toHaveBeenCalled();
|
|
122
|
+
expect(recoveryAgent.run).not.toHaveBeenCalled();
|
|
123
|
+
expect(feedback.send).not.toHaveBeenCalled();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('skips work entirely when claimNext returns null (idle queue)', async () => {
|
|
127
|
+
(service.claimNext as ReturnType<typeof vi.fn>).mockResolvedValueOnce(null);
|
|
128
|
+
|
|
129
|
+
await worker.drainOnce();
|
|
130
|
+
|
|
131
|
+
expect(workflow.runPendingCommit).not.toHaveBeenCalled();
|
|
132
|
+
expect(service.markSucceeded).not.toHaveBeenCalled();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('transient failure (within budget) records the error and does NOT spawn recovery', async () => {
|
|
136
|
+
// attempts=0 → next attempt would be 1, which is < N_TRANSIENT (3).
|
|
137
|
+
const row = makeRow({ attempts: 0 });
|
|
138
|
+
(service.claimNext as ReturnType<typeof vi.fn>).mockResolvedValueOnce(row);
|
|
139
|
+
workflow.runPendingCommit.mockRejectedValueOnce(new Error('transient git error'));
|
|
140
|
+
|
|
141
|
+
await worker.drainOnce();
|
|
142
|
+
|
|
143
|
+
expect(service.markTransientFailure).toHaveBeenCalledWith('row-1', 'transient git error');
|
|
144
|
+
expect(service.markRecoveryStarted).not.toHaveBeenCalled();
|
|
145
|
+
expect(recoveryAgent.run).not.toHaveBeenCalled();
|
|
146
|
+
expect(service.markNeedsAttention).not.toHaveBeenCalled();
|
|
147
|
+
expect(feedback.send).not.toHaveBeenCalled();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('exhausting transient budget spawns the recovery agent (within recovery budget)', async () => {
|
|
151
|
+
// attempts = N_TRANSIENT - 1 → next attempt would equal N_TRANSIENT,
|
|
152
|
+
// crossing the budget. recoveryAgentRuns=0 → still within recovery budget.
|
|
153
|
+
const row = makeRow({ attempts: N_TRANSIENT - 1, recoveryAgentRuns: 0 });
|
|
154
|
+
(service.claimNext as ReturnType<typeof vi.fn>).mockResolvedValueOnce(row);
|
|
155
|
+
workflow.runPendingCommit.mockRejectedValueOnce(new Error('cannot push: needs human'));
|
|
156
|
+
recoveryAgent.run.mockResolvedValueOnce(undefined);
|
|
157
|
+
|
|
158
|
+
await worker.drainOnce();
|
|
159
|
+
|
|
160
|
+
expect(service.markTransientFailure).not.toHaveBeenCalled();
|
|
161
|
+
expect(service.markRecoveryStarted).toHaveBeenCalledWith('row-1');
|
|
162
|
+
expect(recoveryAgent.run).toHaveBeenCalledWith({
|
|
163
|
+
workspaceId: 'ws-1',
|
|
164
|
+
branch: 'feat/x',
|
|
165
|
+
path: 'Foo.md',
|
|
166
|
+
lastError: 'cannot push: needs human',
|
|
167
|
+
originalAuthorEmail: 'alice@example.com',
|
|
168
|
+
});
|
|
169
|
+
expect(service.markNeedsAttention).not.toHaveBeenCalled();
|
|
170
|
+
expect(feedback.send).not.toHaveBeenCalled();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('a recovery-agent crash does not abort the worker — the row stays pending for the next pass', async () => {
|
|
174
|
+
const row = makeRow({ attempts: N_TRANSIENT - 1, recoveryAgentRuns: 0 });
|
|
175
|
+
(service.claimNext as ReturnType<typeof vi.fn>).mockResolvedValueOnce(row);
|
|
176
|
+
workflow.runPendingCommit.mockRejectedValueOnce(new Error('cannot push'));
|
|
177
|
+
recoveryAgent.run.mockRejectedValueOnce(new Error('codemode wiring broken'));
|
|
178
|
+
|
|
179
|
+
// No throw — the worker swallows the agent crash and moves on.
|
|
180
|
+
await expect(worker.drainOnce()).resolves.toBeUndefined();
|
|
181
|
+
// markRecoveryStarted still fired (we incremented the agent counter
|
|
182
|
+
// for this attempt), so the row will hit needs_attention naturally
|
|
183
|
+
// after N_RECOVERY tries.
|
|
184
|
+
expect(service.markRecoveryStarted).toHaveBeenCalled();
|
|
185
|
+
expect(service.markNeedsAttention).not.toHaveBeenCalled();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('exhausting both budgets marks needs_attention and emits a system feedback notice', async () => {
|
|
189
|
+
// attempts at the transient ceiling AND recovery already ran N_RECOVERY
|
|
190
|
+
// times: nothing left to try. Escalate.
|
|
191
|
+
const row = makeRow({
|
|
192
|
+
attempts: N_TRANSIENT - 1,
|
|
193
|
+
recoveryAgentRuns: N_RECOVERY,
|
|
194
|
+
});
|
|
195
|
+
(service.claimNext as ReturnType<typeof vi.fn>).mockResolvedValueOnce(row);
|
|
196
|
+
workflow.runPendingCommit.mockRejectedValueOnce(new Error('unrecoverable'));
|
|
197
|
+
|
|
198
|
+
await worker.drainOnce();
|
|
199
|
+
|
|
200
|
+
expect(service.markNeedsAttention).toHaveBeenCalledWith('row-1', 'unrecoverable');
|
|
201
|
+
expect(recoveryAgent.run).not.toHaveBeenCalled();
|
|
202
|
+
expect(feedback.send).toHaveBeenCalledTimes(1);
|
|
203
|
+
const call = (feedback.send as ReturnType<typeof vi.fn>).mock.calls[0][0];
|
|
204
|
+
expect(call.source).toBe('system');
|
|
205
|
+
expect(call.user).toEqual(RECOVERY_BOT);
|
|
206
|
+
// Body should carry enough context for an admin to investigate without
|
|
207
|
+
// needing to consult the DB.
|
|
208
|
+
expect(call.message).toContain('Terminal commit failure');
|
|
209
|
+
expect(call.message).toContain('ws-1');
|
|
210
|
+
expect(call.message).toContain('Foo.md');
|
|
211
|
+
expect(call.message).toContain('unrecoverable');
|
|
212
|
+
expect(call.message).toContain('alice@example.com');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('a feedback-sink failure does not throw — the row stays needs_attention regardless', async () => {
|
|
216
|
+
const row = makeRow({ attempts: N_TRANSIENT - 1, recoveryAgentRuns: N_RECOVERY });
|
|
217
|
+
(service.claimNext as ReturnType<typeof vi.fn>).mockResolvedValueOnce(row);
|
|
218
|
+
workflow.runPendingCommit.mockRejectedValueOnce(new Error('unrecoverable'));
|
|
219
|
+
(feedback.send as ReturnType<typeof vi.fn>).mockRejectedValueOnce(new Error('feedback DB down'));
|
|
220
|
+
|
|
221
|
+
await expect(worker.drainOnce()).resolves.toBeUndefined();
|
|
222
|
+
expect(service.markNeedsAttention).toHaveBeenCalled();
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe('PendingCommitsWorker lifecycle', () => {
|
|
227
|
+
it('start() is idempotent; stop() awaits the in-flight pass without throwing', async () => {
|
|
228
|
+
const worker = new PendingCommitsWorker({
|
|
229
|
+
service: makeService(),
|
|
230
|
+
workflow: { runPendingCommit: vi.fn() },
|
|
231
|
+
recoveryAgent: { run: vi.fn() },
|
|
232
|
+
feedback: makeFeedback(),
|
|
233
|
+
workspaces: makeWorkspaces(),
|
|
234
|
+
recoveryBot: RECOVERY_BOT,
|
|
235
|
+
// Skip the real sleep so stop() returns immediately.
|
|
236
|
+
sleep: () => Promise.resolve(),
|
|
237
|
+
});
|
|
238
|
+
worker.start();
|
|
239
|
+
worker.start(); // second start is a no-op
|
|
240
|
+
await expect(worker.stop()).resolves.toBeUndefined();
|
|
241
|
+
await expect(worker.stop()).resolves.toBeUndefined(); // idempotent
|
|
242
|
+
});
|
|
243
|
+
});
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { promises as fs } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
|
|
6
|
+
import type { GitService } from '../git/git.service.js';
|
|
7
|
+
import type { PullRequestService } from '../git/pull-request.service.js';
|
|
8
|
+
import type { IReviewWorkflowService } from '../review-workflow/review-workflow.interface.js';
|
|
9
|
+
import type { WorkspaceService } from '../../workspace/workspace.service.js';
|
|
10
|
+
import type { IAccessControl } from '../../access/access-control.interface.js';
|
|
11
|
+
import { FileLockService } from '../file-lock.service.js';
|
|
12
|
+
import { PendingCommitsService } from '../pending-commits.service.js';
|
|
13
|
+
import { WorkflowService } from '../workflow.service.js';
|
|
14
|
+
import type { Database } from '../../database/connection.js';
|
|
15
|
+
import { RolesYamlPreservationError } from '../workflow.errors.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Focused tests for the roles.yaml-preservation guard inside
|
|
19
|
+
* `mergeChangeRequest`. The security property: a change request can NEVER carry
|
|
20
|
+
* a roles.yaml change across its merge — if the CR's roles.yaml differs from the
|
|
21
|
+
* base branch's, the base version is restored on the SOURCE branch (commit +
|
|
22
|
+
* push) before the merge fires, so the merged diff has no roles.yaml change.
|
|
23
|
+
* roles.yaml is mutable only via the admin Roles & Members surface.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const KB_DIR = 'knowledge-base';
|
|
27
|
+
const BASE = 'current-company-state';
|
|
28
|
+
const HEAD = 'mallory/escalate';
|
|
29
|
+
const USER = { id: 'u-mallory', email: 'mallory@x.com', name: 'Mallory' };
|
|
30
|
+
const BASE_ROLES = 'roles:\n Admin:\n - admin@x.com\n';
|
|
31
|
+
const ATTACKER_ROLES = 'roles:\n Admin:\n - admin@x.com\n - mallory@x.com\n';
|
|
32
|
+
|
|
33
|
+
function noopAccessControl(): IAccessControl {
|
|
34
|
+
return {
|
|
35
|
+
canWrite: vi.fn(), canWriteBatch: vi.fn(), canRead: vi.fn(), canReadBatch: vi.fn(),
|
|
36
|
+
eligibleReaders: vi.fn(), canReadAtRef: vi.fn(),
|
|
37
|
+
canDownload: vi.fn(), canOwner: vi.fn(), eligibleOwners: vi.fn(), eligibleDownloaders: vi.fn(),
|
|
38
|
+
eligibleWriters: vi.fn(), eligibleWriterEmails: vi.fn(), eligibleOwnerEmails: vi.fn(),
|
|
39
|
+
grantSources: vi.fn(), invalidate: vi.fn(), findEmailByHash: vi.fn(), kbPrincipals: vi.fn(),
|
|
40
|
+
validateRolesYaml: vi.fn(), referencesToRole: vi.fn(), canWriteAtRef: vi.fn(),
|
|
41
|
+
canWriteBatchAtRef: vi.fn(), eligibleWritersAtRef: vi.fn(), eligibleWritersForPathsAtRef: vi.fn(),
|
|
42
|
+
} as unknown as IAccessControl;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe('mergeChangeRequest — roles.yaml preservation guard', () => {
|
|
46
|
+
let root: string;
|
|
47
|
+
let headRepoDir: string;
|
|
48
|
+
|
|
49
|
+
beforeEach(async () => {
|
|
50
|
+
root = await fs.mkdtemp(path.join(os.tmpdir(), 'preserve-roles-'));
|
|
51
|
+
// The source branch's per-branch workspace, laid out like the real one:
|
|
52
|
+
// <root>/<encoded-branch>/<kbDir>/roles.yaml
|
|
53
|
+
headRepoDir = path.join(root, encodeURIComponent(HEAD), KB_DIR);
|
|
54
|
+
await fs.mkdir(headRepoDir, { recursive: true });
|
|
55
|
+
});
|
|
56
|
+
afterEach(async () => {
|
|
57
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
function makeSvc(opts: {
|
|
61
|
+
headRoles: string | null;
|
|
62
|
+
baseRoles: string | null;
|
|
63
|
+
commitFileResult?: unknown;
|
|
64
|
+
pushImpl?: () => Promise<void>;
|
|
65
|
+
getPrResult?: { branch: string; base: string } | null;
|
|
66
|
+
getPrDetailResult?: unknown;
|
|
67
|
+
// Simulate a concurrent editor already holding the roles.yaml lock. When set,
|
|
68
|
+
// acquire returns { acquired: false } and the restore must fail-closed.
|
|
69
|
+
lockHeldBy?: string;
|
|
70
|
+
}) {
|
|
71
|
+
const ac = noopAccessControl();
|
|
72
|
+
const reviewWorkflow = {
|
|
73
|
+
mergePr: vi.fn().mockResolvedValue({ prNumber: 1, sha: 'merged', mergedAt: 't' }),
|
|
74
|
+
} as unknown as IReviewWorkflowService;
|
|
75
|
+
|
|
76
|
+
const workspaceService = {
|
|
77
|
+
getOrCreateForBranch: vi.fn(async (branch: string) => ({ id: encodeURIComponent(branch) })),
|
|
78
|
+
getWorkspacePath: vi.fn(async (id: string) => path.join(root, id)),
|
|
79
|
+
} as unknown as WorkspaceService;
|
|
80
|
+
|
|
81
|
+
const git = {
|
|
82
|
+
fetch: vi.fn().mockResolvedValue(undefined),
|
|
83
|
+
pull: vi.fn().mockResolvedValue(undefined),
|
|
84
|
+
// Resolve roles.yaml at origin/<ref> from the per-test content.
|
|
85
|
+
readFileAtRef: vi.fn(async (_ws: string, ref: string) =>
|
|
86
|
+
ref === `origin/${BASE}` ? opts.baseRoles : ref === `origin/${HEAD}` ? opts.headRoles : null,
|
|
87
|
+
),
|
|
88
|
+
commitFile: vi.fn().mockResolvedValue(
|
|
89
|
+
'commitFileResult' in opts ? opts.commitFileResult : { sha: 'preserve-sha' },
|
|
90
|
+
),
|
|
91
|
+
push: vi.fn(opts.pushImpl ?? (async () => undefined)),
|
|
92
|
+
} as unknown as GitService;
|
|
93
|
+
|
|
94
|
+
const prs = {
|
|
95
|
+
getPr: vi.fn().mockResolvedValue(
|
|
96
|
+
opts.getPrResult === undefined ? { branch: HEAD, base: BASE } : opts.getPrResult,
|
|
97
|
+
),
|
|
98
|
+
// After a preservation commit, mergeChangeRequest refreshes the CR detail
|
|
99
|
+
// (fresh headSha + approvals) before merging. Deterministic stub.
|
|
100
|
+
getPrDetail: vi.fn().mockResolvedValue(
|
|
101
|
+
'getPrDetailResult' in opts
|
|
102
|
+
? opts.getPrDetailResult
|
|
103
|
+
: { headSha: 'sha-after-preserve', approvals: [], state: 'open', title: 'Title', base: BASE },
|
|
104
|
+
),
|
|
105
|
+
invalidateDetailCache: vi.fn(),
|
|
106
|
+
} as unknown as PullRequestService;
|
|
107
|
+
|
|
108
|
+
// The restore holds the SAME per-file lock a concurrent editor would (keyed
|
|
109
|
+
// on `<kbDir>/roles.yaml`), so a live edit can't race the neutralisation.
|
|
110
|
+
const acquire = vi.fn(async () =>
|
|
111
|
+
opts.lockHeldBy
|
|
112
|
+
? { acquired: false as const, lock: { holderName: opts.lockHeldBy } }
|
|
113
|
+
: { acquired: true as const, lock: { holderName: USER.name } },
|
|
114
|
+
);
|
|
115
|
+
const release = vi.fn(async () => undefined);
|
|
116
|
+
const fileLocks = { acquire, release } as unknown as FileLockService;
|
|
117
|
+
|
|
118
|
+
const svc = new WorkflowService(
|
|
119
|
+
{} as unknown as Database,
|
|
120
|
+
git, prs, reviewWorkflow, workspaceService, ac,
|
|
121
|
+
fileLocks, {} as unknown as PendingCommitsService,
|
|
122
|
+
KB_DIR,
|
|
123
|
+
);
|
|
124
|
+
// Conflicts are now surfaced by the local merge inside `reviewWorkflow.mergePr`
|
|
125
|
+
// (mocked to resolve here), so there's no provider "mergeable" pre-check to stub.
|
|
126
|
+
return { svc, git, prs, reviewWorkflow, fileLocks };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function merge(svc: WorkflowService) {
|
|
130
|
+
return svc.mergeChangeRequest(1, USER, 'sha', [], 'open', 'Title', BASE, 'w1', { bypass: false });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
it('no-ops when the CR does not change roles.yaml (identical content)', async () => {
|
|
134
|
+
const { svc, git, reviewWorkflow } = makeSvc({ headRoles: BASE_ROLES, baseRoles: BASE_ROLES });
|
|
135
|
+
await merge(svc);
|
|
136
|
+
// No restore write/commit/push, but the merge still proceeds.
|
|
137
|
+
expect(git.commitFile).not.toHaveBeenCalled();
|
|
138
|
+
expect(git.push).not.toHaveBeenCalled();
|
|
139
|
+
expect(reviewWorkflow.mergePr).toHaveBeenCalledTimes(1);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('no-ops when neither base nor head has a roles.yaml (both absent)', async () => {
|
|
143
|
+
const { svc, git, reviewWorkflow } = makeSvc({ headRoles: null, baseRoles: null });
|
|
144
|
+
await merge(svc);
|
|
145
|
+
expect(git.commitFile).not.toHaveBeenCalled();
|
|
146
|
+
expect(reviewWorkflow.mergePr).toHaveBeenCalledTimes(1);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('restores base roles.yaml on the source branch when the CR changed it (escalation guard)', async () => {
|
|
150
|
+
// Seed the source workspace with the ATTACKER version, as it would be after
|
|
151
|
+
// reset-to-origin of the CR head.
|
|
152
|
+
await fs.writeFile(path.join(headRepoDir, 'roles.yaml'), ATTACKER_ROLES);
|
|
153
|
+
const { svc, git, reviewWorkflow, fileLocks } = makeSvc({ headRoles: ATTACKER_ROLES, baseRoles: BASE_ROLES });
|
|
154
|
+
|
|
155
|
+
await merge(svc);
|
|
156
|
+
|
|
157
|
+
// The restore ran under the SAME per-file lock a human/agent editor uses,
|
|
158
|
+
// keyed on the workspace-relative `<kbDir>/roles.yaml` path on the SOURCE
|
|
159
|
+
// branch — so a concurrent edit can't slip past the neutralisation.
|
|
160
|
+
expect(fileLocks.acquire).toHaveBeenCalledWith(
|
|
161
|
+
encodeURIComponent(HEAD),
|
|
162
|
+
HEAD,
|
|
163
|
+
`${KB_DIR}/roles.yaml`,
|
|
164
|
+
USER,
|
|
165
|
+
);
|
|
166
|
+
// The working-tree roles.yaml was rewritten back to the BASE version...
|
|
167
|
+
const onDisk = await fs.readFile(path.join(headRepoDir, 'roles.yaml'), 'utf-8');
|
|
168
|
+
expect(onDisk).toBe(BASE_ROLES);
|
|
169
|
+
// ...committed (single-path, not `add -A`) + pushed on the SOURCE branch...
|
|
170
|
+
expect(git.commitFile).toHaveBeenCalledWith(
|
|
171
|
+
encodeURIComponent(HEAD),
|
|
172
|
+
USER,
|
|
173
|
+
'roles.yaml',
|
|
174
|
+
expect.stringMatching(/preserve official roles\.yaml/i),
|
|
175
|
+
true,
|
|
176
|
+
);
|
|
177
|
+
expect(git.push).toHaveBeenCalledWith(encodeURIComponent(HEAD), USER);
|
|
178
|
+
// ...the lock was released after the inline commit...
|
|
179
|
+
expect(fileLocks.release).toHaveBeenCalledWith(
|
|
180
|
+
encodeURIComponent(HEAD),
|
|
181
|
+
HEAD,
|
|
182
|
+
`${KB_DIR}/roles.yaml`,
|
|
183
|
+
USER,
|
|
184
|
+
);
|
|
185
|
+
// ...and only THEN does the merge proceed.
|
|
186
|
+
expect(reviewWorkflow.mergePr).toHaveBeenCalledTimes(1);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('ABORTS the merge when roles.yaml is locked by a concurrent editor (fail-closed)', async () => {
|
|
190
|
+
// A same-branch editor holds the roles.yaml lock. The restore must NOT race
|
|
191
|
+
// it — acquire returns { acquired: false }, so the merge aborts rather than
|
|
192
|
+
// writing over (or being overwritten by) the live edit.
|
|
193
|
+
await fs.writeFile(path.join(headRepoDir, 'roles.yaml'), ATTACKER_ROLES);
|
|
194
|
+
const { svc, git, reviewWorkflow } = makeSvc({
|
|
195
|
+
headRoles: ATTACKER_ROLES,
|
|
196
|
+
baseRoles: BASE_ROLES,
|
|
197
|
+
lockHeldBy: 'Someone Else',
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
await expect(merge(svc)).rejects.toBeInstanceOf(RolesYamlPreservationError);
|
|
201
|
+
// Nothing was written/committed/pushed, and the merge never ran.
|
|
202
|
+
expect(git.commitFile).not.toHaveBeenCalled();
|
|
203
|
+
expect(git.push).not.toHaveBeenCalled();
|
|
204
|
+
expect(reviewWorkflow.mergePr).not.toHaveBeenCalled();
|
|
205
|
+
// The on-disk attacker version is untouched — the lock holder's edit stands.
|
|
206
|
+
const onDisk = await fs.readFile(path.join(headRepoDir, 'roles.yaml'), 'utf-8');
|
|
207
|
+
expect(onDisk).toBe(ATTACKER_ROLES);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('restores by deleting roles.yaml when the base branch has none', async () => {
|
|
211
|
+
await fs.writeFile(path.join(headRepoDir, 'roles.yaml'), ATTACKER_ROLES);
|
|
212
|
+
const { svc, git } = makeSvc({ headRoles: ATTACKER_ROLES, baseRoles: null });
|
|
213
|
+
|
|
214
|
+
await merge(svc);
|
|
215
|
+
|
|
216
|
+
await expect(fs.access(path.join(headRepoDir, 'roles.yaml'))).rejects.toThrow();
|
|
217
|
+
expect(git.commitFile).toHaveBeenCalled();
|
|
218
|
+
expect(git.push).toHaveBeenCalled();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('ABORTS the merge (RolesYamlPreservationError) if the restore push fails', async () => {
|
|
222
|
+
await fs.writeFile(path.join(headRepoDir, 'roles.yaml'), ATTACKER_ROLES);
|
|
223
|
+
const { svc, reviewWorkflow } = makeSvc({
|
|
224
|
+
headRoles: ATTACKER_ROLES,
|
|
225
|
+
baseRoles: BASE_ROLES,
|
|
226
|
+
pushImpl: async () => { throw new Error('push rejected: non-fast-forward'); },
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
await expect(merge(svc)).rejects.toBeInstanceOf(RolesYamlPreservationError);
|
|
230
|
+
// Fail-closed: the merge must NOT have run.
|
|
231
|
+
expect(reviewWorkflow.mergePr).not.toHaveBeenCalled();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('ABORTS the merge if the CR (PR) cannot be resolved', async () => {
|
|
235
|
+
const { svc, reviewWorkflow } = makeSvc({ headRoles: BASE_ROLES, baseRoles: BASE_ROLES, getPrResult: null });
|
|
236
|
+
await expect(merge(svc)).rejects.toBeInstanceOf(RolesYamlPreservationError);
|
|
237
|
+
expect(reviewWorkflow.mergePr).not.toHaveBeenCalled();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('refreshes the merge inputs from the updated PR head after a preservation commit', async () => {
|
|
241
|
+
// The restore commit advances the source head, so the caller's headSha/approvals
|
|
242
|
+
// (resolved pre-preservation) go stale. mergeChangeRequest must re-resolve them
|
|
243
|
+
// from the live PR before merging, not forward the originals.
|
|
244
|
+
await fs.writeFile(path.join(headRepoDir, 'roles.yaml'), ATTACKER_ROLES);
|
|
245
|
+
const { svc, prs, reviewWorkflow } = makeSvc({ headRoles: ATTACKER_ROLES, baseRoles: BASE_ROLES });
|
|
246
|
+
|
|
247
|
+
await merge(svc);
|
|
248
|
+
|
|
249
|
+
expect(prs.getPrDetail).toHaveBeenCalledWith(1, expect.objectContaining({ fresh: true }));
|
|
250
|
+
// mergePr is called with the REFRESHED head sha, not the original 'sha'.
|
|
251
|
+
expect(reviewWorkflow.mergePr).toHaveBeenCalledWith(
|
|
252
|
+
1, USER, 'sha-after-preserve', [], 'open', 'Title', BASE, 'w1', { bypass: false },
|
|
253
|
+
);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('does NOT refresh (keeps the caller inputs) when the CR does not touch roles.yaml', async () => {
|
|
257
|
+
// No preservation commit → no head advance → nothing to refresh; the original
|
|
258
|
+
// headSha/approvals flow straight through.
|
|
259
|
+
const { svc, prs, reviewWorkflow } = makeSvc({ headRoles: BASE_ROLES, baseRoles: BASE_ROLES });
|
|
260
|
+
await merge(svc);
|
|
261
|
+
expect(prs.getPrDetail).not.toHaveBeenCalled();
|
|
262
|
+
expect(reviewWorkflow.mergePr).toHaveBeenCalledWith(
|
|
263
|
+
1, USER, 'sha', [], 'open', 'Title', BASE, 'w1', { bypass: false },
|
|
264
|
+
);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('ABORTS the merge when the restore produces no commit while origin still diverges (fail-closed)', async () => {
|
|
268
|
+
// origin/base vs origin/head differ (restore required), but commitFile is a
|
|
269
|
+
// no-op — e.g. the local checkout is out of sync and already matched base.
|
|
270
|
+
// origin/head would still carry the divergent roles.yaml, so this must fail
|
|
271
|
+
// closed rather than return "nothing to do".
|
|
272
|
+
await fs.writeFile(path.join(headRepoDir, 'roles.yaml'), BASE_ROLES);
|
|
273
|
+
const { svc, git, reviewWorkflow } = makeSvc({
|
|
274
|
+
headRoles: ATTACKER_ROLES,
|
|
275
|
+
baseRoles: BASE_ROLES,
|
|
276
|
+
commitFileResult: null,
|
|
277
|
+
});
|
|
278
|
+
await expect(merge(svc)).rejects.toBeInstanceOf(RolesYamlPreservationError);
|
|
279
|
+
expect(git.push).not.toHaveBeenCalled();
|
|
280
|
+
expect(reviewWorkflow.mergePr).not.toHaveBeenCalled();
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('ABORTS the merge when the post-preservation CR detail cannot be reloaded (fail-closed)', async () => {
|
|
284
|
+
// After a preservation commit advances the head, the gate inputs must be
|
|
285
|
+
// re-resolved. If the fresh detail is unavailable, abort — never fall back to
|
|
286
|
+
// the stale headSha/approvals.
|
|
287
|
+
await fs.writeFile(path.join(headRepoDir, 'roles.yaml'), ATTACKER_ROLES);
|
|
288
|
+
const { svc, reviewWorkflow } = makeSvc({
|
|
289
|
+
headRoles: ATTACKER_ROLES,
|
|
290
|
+
baseRoles: BASE_ROLES,
|
|
291
|
+
getPrDetailResult: null,
|
|
292
|
+
});
|
|
293
|
+
await expect(merge(svc)).rejects.toBeInstanceOf(RolesYamlPreservationError);
|
|
294
|
+
expect(reviewWorkflow.mergePr).not.toHaveBeenCalled();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('ABORTS the merge when reading roles.yaml fails for a non-absence reason (fail-closed)', async () => {
|
|
298
|
+
// readFileAtRef now rethrows real git errors (only true absence maps to null),
|
|
299
|
+
// so a fatal read must surface as RolesYamlPreservationError — never be mistaken
|
|
300
|
+
// for "roles.yaml absent" and waved through.
|
|
301
|
+
const { svc, git, reviewWorkflow } = makeSvc({ headRoles: ATTACKER_ROLES, baseRoles: BASE_ROLES });
|
|
302
|
+
(git.readFileAtRef as unknown as ReturnType<typeof vi.fn>).mockRejectedValue(
|
|
303
|
+
new Error('git show failed: fatal: unable to read tree object'),
|
|
304
|
+
);
|
|
305
|
+
await expect(merge(svc)).rejects.toBeInstanceOf(RolesYamlPreservationError);
|
|
306
|
+
expect(reviewWorkflow.mergePr).not.toHaveBeenCalled();
|
|
307
|
+
});
|
|
308
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { sanitizeError } from '../sanitize-error.js';
|
|
3
|
+
|
|
4
|
+
describe('sanitizeError', () => {
|
|
5
|
+
it('returns String(err) for non-Error throws', () => {
|
|
6
|
+
expect(sanitizeError('plain string')).toBe('plain string');
|
|
7
|
+
expect(sanitizeError(42)).toBe('42');
|
|
8
|
+
expect(sanitizeError(null)).toBe('null');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('extracts err.message for Error instances and drops the stack', () => {
|
|
12
|
+
const err = new Error('boom');
|
|
13
|
+
err.stack = 'Error: boom\n at /secret/path/file.ts:1:1\n authorization: bearer abc';
|
|
14
|
+
expect(sanitizeError(err)).toBe('boom');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('collapses whitespace to a single line', () => {
|
|
18
|
+
expect(sanitizeError(new Error('line1\nline2\n\tline3'))).toBe('line1 line2 line3');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('redacts the user:pass component of a credentialed URL but keeps the host', () => {
|
|
22
|
+
const msg = "fatal: unable to access 'https://x-access-token:ghp_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@github.com/acme/repo.git/'";
|
|
23
|
+
const out = sanitizeError(new Error(msg));
|
|
24
|
+
expect(out).not.toContain('ghp_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
|
|
25
|
+
expect(out).not.toContain('x-access-token');
|
|
26
|
+
expect(out).toContain('[REDACTED]@');
|
|
27
|
+
expect(out).toContain('github.com');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('redacts Authorization: Bearer headers', () => {
|
|
31
|
+
const out = sanitizeError(
|
|
32
|
+
new Error('http 401 (Authorization: Bearer sk-AAAAAAAAAAAAAAAAAAAA)'),
|
|
33
|
+
);
|
|
34
|
+
expect(out).not.toContain('sk-AAAAAAAAAAAAAAAAAAAA');
|
|
35
|
+
expect(out).toContain('[REDACTED]');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('redacts token= / secret= / api_key= style key-value pairs', () => {
|
|
39
|
+
const out = sanitizeError(new Error('curl https://host?token=AAAAAAAAAAAAAAAA secret=BBBBBBBBBBBBBBBB'));
|
|
40
|
+
expect(out).not.toContain('AAAAAAAAAAAAAAAA');
|
|
41
|
+
expect(out).not.toContain('BBBBBBBBBBBBBBBB');
|
|
42
|
+
expect(out.match(/\[REDACTED\]/g)?.length).toBeGreaterThanOrEqual(2);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('redacts long bare hex blobs that look like tokens', () => {
|
|
46
|
+
const out = sanitizeError(new Error('sha=a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6'));
|
|
47
|
+
expect(out).not.toContain('a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('redacts GitHub PAT prefixes (ghp_, github_pat_)', () => {
|
|
51
|
+
expect(sanitizeError(new Error('use ghp_abcdef1234567890'))).not.toContain('ghp_abcdef1234567890');
|
|
52
|
+
expect(sanitizeError(new Error('use github_pat_11ABCDEFGH_xxx'))).not.toContain('github_pat_11ABCDEFGH_xxx');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('truncates messages longer than 200 chars with an ellipsis', () => {
|
|
56
|
+
const long = 'x'.repeat(500);
|
|
57
|
+
const out = sanitizeError(new Error(long));
|
|
58
|
+
expect(out.length).toBeLessThanOrEqual(200);
|
|
59
|
+
expect(out.endsWith('…')).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('is idempotent on already-sanitised text', () => {
|
|
63
|
+
const once = sanitizeError(new Error('Authorization: Bearer abc; token=def123'));
|
|
64
|
+
const twice = sanitizeError(once);
|
|
65
|
+
expect(twice).toBe(once);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { recordTouch, decideWrite } from '../session-ontology.policy.js';
|
|
3
|
+
|
|
4
|
+
const PRODUCT = 'KnowledgeBase/Product';
|
|
5
|
+
const PLATFORM = 'KnowledgeBase/Platform';
|
|
6
|
+
const GTM = 'KnowledgeBase/GTM';
|
|
7
|
+
|
|
8
|
+
describe('recordTouch', () => {
|
|
9
|
+
it('adds a named ontology to the set', () => {
|
|
10
|
+
expect([...recordTouch(new Set(), PRODUCT)]).toEqual([PRODUCT]);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('adds nothing for a neutral touch', () => {
|
|
14
|
+
expect([...recordTouch(new Set([PRODUCT]), null)]).toEqual([PRODUCT]);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('accumulates across ontologies', () => {
|
|
18
|
+
const a = recordTouch(new Set(), PRODUCT);
|
|
19
|
+
const b = recordTouch(a, PLATFORM);
|
|
20
|
+
expect(new Set(b)).toEqual(new Set([PRODUCT, PLATFORM]));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('does not mutate the input set', () => {
|
|
24
|
+
const input = new Set([PRODUCT]);
|
|
25
|
+
recordTouch(input, PLATFORM);
|
|
26
|
+
expect([...input]).toEqual([PRODUCT]);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('decideWrite', () => {
|
|
31
|
+
it('allows a write to a neutral path regardless of touched set', () => {
|
|
32
|
+
expect(decideWrite(new Set([PRODUCT, PLATFORM]), null)).toEqual({ allow: true });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('allows a write when the session has touched nothing yet', () => {
|
|
36
|
+
expect(decideWrite(new Set(), PRODUCT)).toEqual({ allow: true });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('allows a write to the single ontology the session is confined to', () => {
|
|
40
|
+
expect(decideWrite(new Set([PRODUCT]), PRODUCT)).toEqual({ allow: true });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('blocks a write to a different ontology when confined to one (different-ontology)', () => {
|
|
44
|
+
expect(decideWrite(new Set([PRODUCT]), PLATFORM)).toEqual({
|
|
45
|
+
allow: false,
|
|
46
|
+
reason: 'different-ontology',
|
|
47
|
+
touched: [PRODUCT],
|
|
48
|
+
attempted: PLATFORM,
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('blocks ALL writes once the session has touched two ontologies (multi-ontology)', () => {
|
|
53
|
+
// Reading across ontologies poisons writes — even a write back into Product.
|
|
54
|
+
expect(decideWrite(new Set([PRODUCT, PLATFORM]), PRODUCT)).toEqual({
|
|
55
|
+
allow: false,
|
|
56
|
+
reason: 'multi-ontology',
|
|
57
|
+
touched: [PLATFORM, PRODUCT],
|
|
58
|
+
attempted: PRODUCT,
|
|
59
|
+
});
|
|
60
|
+
expect(decideWrite(new Set([PRODUCT, PLATFORM]), GTM).allow).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
});
|