@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,247 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
spliceGrant,
|
|
5
|
+
spliceRevoke,
|
|
6
|
+
validatePrincipal,
|
|
7
|
+
AccessSpliceError,
|
|
8
|
+
type Principal,
|
|
9
|
+
} from '../access-splice.js';
|
|
10
|
+
import { parseAccessEntry, canonicalEmail } from '../access-control.service.js';
|
|
11
|
+
|
|
12
|
+
const felix: Principal = { kind: 'user', email: 'felix.kissel@example.com', displayName: 'Felix' };
|
|
13
|
+
const juan: Principal = { kind: 'user', email: 'juan@bevel.software', displayName: 'Juan V' };
|
|
14
|
+
const productTeam: Principal = { kind: 'role', role: 'Product Team' };
|
|
15
|
+
|
|
16
|
+
/** A realistic access.md with a prose body + comment — the thing parse→emit destroys. */
|
|
17
|
+
const ROOT_ACCESS_MD = `---
|
|
18
|
+
write:
|
|
19
|
+
- Admin
|
|
20
|
+
# download is independent of write
|
|
21
|
+
download:
|
|
22
|
+
- Juan <juan@bevel.software>
|
|
23
|
+
owner: []
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
# Repository access
|
|
27
|
+
|
|
28
|
+
This file is the root of the access-control tree. It grants write access only
|
|
29
|
+
to the \`Admin\` role by default. Subfolders can broaden access.
|
|
30
|
+
|
|
31
|
+
See \`lib/access-control.js\` for the resolution semantics.
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
describe('spliceGrant — body + comment preservation (keystone)', () => {
|
|
35
|
+
it('keeps the markdown body, comments, and key order byte-for-byte', () => {
|
|
36
|
+
const r = spliceGrant(ROOT_ACCESS_MD, 'write', felix);
|
|
37
|
+
expect(r.changed).toBe(true);
|
|
38
|
+
// The body prose must survive verbatim.
|
|
39
|
+
expect(r.text).toContain('# Repository access');
|
|
40
|
+
expect(r.text).toContain('See `lib/access-control.js` for the resolution semantics.');
|
|
41
|
+
// The comment must survive.
|
|
42
|
+
expect(r.text).toContain('# download is independent of write');
|
|
43
|
+
// Existing entries untouched, new one appended under write.
|
|
44
|
+
expect(r.text).toContain(' - Admin');
|
|
45
|
+
expect(r.text).toContain(' - Felix <felix.kissel@example.com>');
|
|
46
|
+
// owner stays empty-list.
|
|
47
|
+
expect(r.text).toContain('owner: []');
|
|
48
|
+
// Felix lands under write, immediately after Admin, before the comment.
|
|
49
|
+
const lines = r.text.split('\n');
|
|
50
|
+
const adminIdx = lines.indexOf(' - Admin');
|
|
51
|
+
const felixIdx = lines.indexOf(' - Felix <felix.kissel@example.com>');
|
|
52
|
+
const commentIdx = lines.indexOf('# download is independent of write');
|
|
53
|
+
expect(adminIdx).toBeGreaterThan(-1);
|
|
54
|
+
expect(felixIdx).toBe(adminIdx + 1);
|
|
55
|
+
expect(commentIdx).toBeGreaterThan(felixIdx);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('grant then revoke returns to a body-preserving state', () => {
|
|
59
|
+
const granted = spliceGrant(ROOT_ACCESS_MD, 'write', felix).text;
|
|
60
|
+
const revoked = spliceRevoke(granted, 'write', felix).text;
|
|
61
|
+
expect(revoked).toContain('# Repository access');
|
|
62
|
+
expect(revoked).toContain(' - Admin');
|
|
63
|
+
expect(revoked).not.toContain('Felix');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('spliceGrant — idempotency + creation', () => {
|
|
68
|
+
it('is a no-op when the principal already has the grant', () => {
|
|
69
|
+
const once = spliceGrant(ROOT_ACCESS_MD, 'write', felix).text;
|
|
70
|
+
const twice = spliceGrant(once, 'write', felix);
|
|
71
|
+
expect(twice.changed).toBe(false);
|
|
72
|
+
expect(twice.text).toBe(once);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('creates the verb key when absent', () => {
|
|
76
|
+
const r = spliceGrant(ROOT_ACCESS_MD, 'read', productTeam);
|
|
77
|
+
expect(r.changed).toBe(true);
|
|
78
|
+
expect(r.text).toContain('read:');
|
|
79
|
+
expect(r.text).toContain(' - Product Team');
|
|
80
|
+
expect(r.text).toContain('# Repository access'); // body still there
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('promotes an inline-empty `owner: []` to a block list', () => {
|
|
84
|
+
const r = spliceGrant(ROOT_ACCESS_MD, 'owner', juan);
|
|
85
|
+
expect(r.changed).toBe(true);
|
|
86
|
+
expect(r.text).toContain('owner:\n - Juan V <juan@bevel.software>');
|
|
87
|
+
expect(r.text).not.toContain('owner: []');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('synthesises frontmatter for a fresh (frontmatter-less) file', () => {
|
|
91
|
+
const r = spliceGrant('', 'write', felix);
|
|
92
|
+
expect(r.changed).toBe(true);
|
|
93
|
+
expect(r.text.startsWith('---\n')).toBe(true);
|
|
94
|
+
expect(r.text).toContain('write:\n - Felix <felix.kissel@example.com>');
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('spliceRevoke', () => {
|
|
99
|
+
it('removes one entry and collapses the verb to [] when it empties', () => {
|
|
100
|
+
const r = spliceRevoke(ROOT_ACCESS_MD, 'write', { kind: 'role', role: 'Admin' });
|
|
101
|
+
expect(r.changed).toBe(true);
|
|
102
|
+
expect(r.text).toContain('write: []');
|
|
103
|
+
expect(r.text).not.toMatch(/- Admin/);
|
|
104
|
+
expect(r.text).toContain('# Repository access');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('is a no-op when the principal is not present', () => {
|
|
108
|
+
const r = spliceRevoke(ROOT_ACCESS_MD, 'write', felix);
|
|
109
|
+
expect(r.changed).toBe(false);
|
|
110
|
+
expect(r.text).toBe(ROOT_ACCESS_MD);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('spliceGrant — verbs are independent (a principal may hold several)', () => {
|
|
115
|
+
it('grants a second verb without removing the principal from the first', () => {
|
|
116
|
+
// Verbs do not nest: granting `download` on top of `write` leaves BOTH.
|
|
117
|
+
const granted = spliceGrant(ROOT_ACCESS_MD, 'write', felix).text;
|
|
118
|
+
const both = spliceGrant(granted, 'download', felix).text;
|
|
119
|
+
const writeBlock = both.slice(both.indexOf('write:'), both.indexOf('download:'));
|
|
120
|
+
expect(writeBlock).toContain('Felix'); // still under write
|
|
121
|
+
expect(both).toContain('download:'); // and now under download too
|
|
122
|
+
const downloadBlock = both.slice(both.indexOf('download:'));
|
|
123
|
+
expect(downloadBlock).toContain('Felix');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('is idempotent for an already-granted verb', () => {
|
|
127
|
+
const granted = spliceGrant(ROOT_ACCESS_MD, 'owner', felix).text;
|
|
128
|
+
const again = spliceGrant(granted, 'owner', felix);
|
|
129
|
+
expect(again.changed).toBe(false);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('spliceGrant({ deny }) — the deny-here per-item override', () => {
|
|
134
|
+
it('adds a `deny <principal>` line to an existing block list', () => {
|
|
135
|
+
// `write:` already lists Admin; deny Felix there.
|
|
136
|
+
const r = spliceGrant(ROOT_ACCESS_MD, 'write', felix, { deny: true });
|
|
137
|
+
expect(r.changed).toBe(true);
|
|
138
|
+
const writeBlock = r.text.slice(r.text.indexOf('write:'), r.text.indexOf('download:'));
|
|
139
|
+
expect(writeBlock).toContain('- deny Felix <felix.kissel@example.com>');
|
|
140
|
+
expect(writeBlock).toContain('- Admin'); // existing grant untouched
|
|
141
|
+
// The denial parses back as a deny entry.
|
|
142
|
+
const entry = parseAccessEntry('deny Felix <felix.kissel@example.com>');
|
|
143
|
+
expect(entry.ok && entry.entry.deny).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('promotes a node-frontmatter scalar to a block list with the deny entry', () => {
|
|
147
|
+
// A node whose own frontmatter names a sole owner via the scalar form.
|
|
148
|
+
const node = `---\nnodeType: process\nowner: Juan V <juan@bevel.software>\n---\n# Body\n`;
|
|
149
|
+
const r = spliceGrant(node, 'owner', felix, { deny: true, allowScalar: true });
|
|
150
|
+
expect(r.changed).toBe(true);
|
|
151
|
+
expect(r.text).toContain('owner:');
|
|
152
|
+
expect(r.text).toContain('- Juan V <juan@bevel.software>'); // old scalar preserved
|
|
153
|
+
expect(r.text).toContain('- deny Felix <felix.kissel@example.com>'); // deny added
|
|
154
|
+
expect(r.text).toContain('# Body'); // body survives
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('fills an inline-empty `owner: []` with the deny entry', () => {
|
|
158
|
+
const r = spliceGrant(ROOT_ACCESS_MD, 'owner', felix, { deny: true });
|
|
159
|
+
expect(r.changed).toBe(true);
|
|
160
|
+
const ownerBlock = r.text.slice(r.text.indexOf('owner:'));
|
|
161
|
+
expect(ownerBlock).toContain('- deny Felix <felix.kissel@example.com>');
|
|
162
|
+
expect(ownerBlock).not.toContain('owner: []'); // the [] collapsed to a real list
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('is a no-op when the same deny entry already exists', () => {
|
|
166
|
+
const once = spliceGrant(ROOT_ACCESS_MD, 'write', felix, { deny: true }).text;
|
|
167
|
+
const twice = spliceGrant(once, 'write', felix, { deny: true });
|
|
168
|
+
expect(twice.changed).toBe(false);
|
|
169
|
+
expect(twice.text).toBe(once);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('a deny and a grant for the same principal are independent (deny does not no-op a grant)', () => {
|
|
173
|
+
// An existing grant must NOT make the deny idempotent, and vice-versa —
|
|
174
|
+
// the idempotency key includes deny-ness. (The resolver-level grant-beats-
|
|
175
|
+
// deny interaction is handled one layer up in AccessMutationService.)
|
|
176
|
+
const granted = spliceGrant(ROOT_ACCESS_MD, 'write', felix).text;
|
|
177
|
+
const denied = spliceGrant(granted, 'write', felix, { deny: true });
|
|
178
|
+
expect(denied.changed).toBe(true);
|
|
179
|
+
const writeBlock = denied.text.slice(denied.text.indexOf('write:'), denied.text.indexOf('download:'));
|
|
180
|
+
expect(writeBlock).toContain('- Felix <felix.kissel@example.com>');
|
|
181
|
+
expect(writeBlock).toContain('- deny Felix <felix.kissel@example.com>');
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe('validatePrincipal — injection guard', () => {
|
|
186
|
+
it('rejects a newline-bearing email (frontmatter injection)', () => {
|
|
187
|
+
expect(() =>
|
|
188
|
+
validatePrincipal({
|
|
189
|
+
kind: 'user',
|
|
190
|
+
email: 'x@y.com\nwrite:\n - everyone',
|
|
191
|
+
displayName: 'X',
|
|
192
|
+
}),
|
|
193
|
+
).toThrow(AccessSpliceError);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('rejects a display name containing angle brackets', () => {
|
|
197
|
+
expect(() =>
|
|
198
|
+
validatePrincipal({ kind: 'user', email: 'a@b.com', displayName: 'Bad <hack>' }),
|
|
199
|
+
).toThrow(AccessSpliceError);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('rejects a group name with a colon (would forge a key)', () => {
|
|
203
|
+
expect(() => validatePrincipal({ kind: 'role', role: 'write:\n - everyone' })).toThrow(
|
|
204
|
+
AccessSpliceError,
|
|
205
|
+
);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('rejects the reserved group name deny', () => {
|
|
209
|
+
expect(() => validatePrincipal({ kind: 'role', role: 'deny' })).toThrow(AccessSpliceError);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('accepts the built-in everyone role (grantable as public read; route gates the verb)', () => {
|
|
213
|
+
expect(validatePrincipal({ kind: 'role', role: 'everyone' })).toEqual({
|
|
214
|
+
kind: 'role',
|
|
215
|
+
role: 'everyone',
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('rejects `#` in a name (uncomment() would truncate it on read-back)', () => {
|
|
220
|
+
expect(() =>
|
|
221
|
+
validatePrincipal({ kind: 'user', email: 'a@b.com', displayName: 'Felix #1' }),
|
|
222
|
+
).toThrow(AccessSpliceError);
|
|
223
|
+
expect(() => validatePrincipal({ kind: 'role', role: 'Team #1' })).toThrow(AccessSpliceError);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('canonicalises a valid principal (lowercased email)', () => {
|
|
227
|
+
const p = validatePrincipal({ kind: 'user', email: 'Felix@Example.COM', displayName: 'Felix' });
|
|
228
|
+
expect(p.kind === 'user' && p.email).toBe('felix@example.com');
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe('round-trip — every granted entry parses back', () => {
|
|
233
|
+
it('a granted person renders to a line parseAccessEntry accepts', () => {
|
|
234
|
+
const r = spliceGrant(ROOT_ACCESS_MD, 'write', felix);
|
|
235
|
+
const line = r.text
|
|
236
|
+
.split('\n')
|
|
237
|
+
.find((l) => l.trim().startsWith('- Felix'))!
|
|
238
|
+
.trim()
|
|
239
|
+
.slice(2)
|
|
240
|
+
.trim();
|
|
241
|
+
const parsed = parseAccessEntry(line);
|
|
242
|
+
expect(parsed.ok).toBe(true);
|
|
243
|
+
expect(parsed.ok && parsed.entry.kind === 'user' && parsed.entry.email).toBe(
|
|
244
|
+
canonicalEmail(felix.kind === 'user' ? felix.email : ''),
|
|
245
|
+
);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import type { Server } from 'node:http';
|
|
2
|
+
import type { AddressInfo } from 'node:net';
|
|
3
|
+
import express from 'express';
|
|
4
|
+
import { describe, it, expect, afterEach, vi } from 'vitest';
|
|
5
|
+
|
|
6
|
+
import type { IAccessControl, GrantSources } from '../access-control.interface.js';
|
|
7
|
+
import type { WorkspaceService } from '../../workspace/workspace.service.js';
|
|
8
|
+
import type { AuthService } from '../../auth/auth.service.js';
|
|
9
|
+
import type { WorkflowService } from '../../workflow/workflow.service.js';
|
|
10
|
+
import type { WorkflowEventBus } from '../../workflow/event-bus.js';
|
|
11
|
+
import type { Database } from '../../database/connection.js';
|
|
12
|
+
import { createAccessRoutes } from '../access.routes.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* HTTP-level contract tests for the revoke route's CLASSIFICATION + MODE logic —
|
|
16
|
+
* the branching the mutation-layer tests can't reach: the `409 { kind:'inherited' }`
|
|
17
|
+
* body, the 200-vs-409-vs-no-op fork, the ancestor-keyed 403 gate, and the
|
|
18
|
+
* stale-ancestor TOCTOU abort. The resolver/mutation are stubbed so each test
|
|
19
|
+
* pins exactly one route decision; the in-memory file store lets the real
|
|
20
|
+
* `AccessMutationService` splice run end-to-end under the route's lock wrapper.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const USER = { id: 'u-1', email: 'alice@bevel.software', name: 'Alice' };
|
|
24
|
+
const WS = 'alice/feature'; // a non-protected feature branch
|
|
25
|
+
const KB = 'knowledge-base';
|
|
26
|
+
|
|
27
|
+
interface Stubs {
|
|
28
|
+
files: Map<string, string>;
|
|
29
|
+
canWrite: ReturnType<typeof vi.fn>;
|
|
30
|
+
grantSources: ReturnType<typeof vi.fn>;
|
|
31
|
+
acquireLock: ReturnType<typeof vi.fn>;
|
|
32
|
+
releaseLock: ReturnType<typeof vi.fn>;
|
|
33
|
+
releaseLockNoCommit: ReturnType<typeof vi.fn>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface Harness {
|
|
37
|
+
server: Server;
|
|
38
|
+
baseUrl: string;
|
|
39
|
+
stubs: Stubs;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function makeHarness(opts: {
|
|
43
|
+
files?: Record<string, string>;
|
|
44
|
+
/** canWrite(path) verdict — defaults to true (writable). */
|
|
45
|
+
canWrite?: (repoRelPath: string) => boolean;
|
|
46
|
+
/** grantSources verdict, by repo-relative target. */
|
|
47
|
+
grantSources?: (repoRelTarget: string) => GrantSources;
|
|
48
|
+
/**
|
|
49
|
+
* Per-email effective-access override for `denyHere`'s post-write assert
|
|
50
|
+
* (canRead/canWrite/canDownload/canOwner). Map an email → false to model a
|
|
51
|
+
* deny that actually removed that principal's access; emails absent from the
|
|
52
|
+
* map fall back to the defaults (read true, others false; canWrite via the
|
|
53
|
+
* `canWrite` opt).
|
|
54
|
+
*/
|
|
55
|
+
effectiveEmails?: Record<string, boolean>;
|
|
56
|
+
/** Override the (otherwise-empty) eligibleWriters list so a principal shows as a row in resolvedView. */
|
|
57
|
+
eligibleWriters?: { roles: string[]; users: { name: string; email: string }[] };
|
|
58
|
+
}): Promise<Harness> {
|
|
59
|
+
const files = new Map<string, string>(Object.entries(opts.files ?? {}));
|
|
60
|
+
|
|
61
|
+
// canWrite is consumed by BOTH the route's pre-lock gate (actor email) and
|
|
62
|
+
// denyHere's post-write effectiveness assert (the denied principal's email).
|
|
63
|
+
// `effectiveEmails` lets a test say "after the deny, THIS email no longer has
|
|
64
|
+
// access" without disturbing the actor's gate verdict.
|
|
65
|
+
const canWrite = vi.fn(async (_w: string, email: string, p: string) => {
|
|
66
|
+
if (opts.effectiveEmails && email in opts.effectiveEmails) return opts.effectiveEmails[email];
|
|
67
|
+
return opts.canWrite ? opts.canWrite(p) : true;
|
|
68
|
+
});
|
|
69
|
+
const grantSources = vi.fn(async (_w: string, _k: string, target: string) =>
|
|
70
|
+
opts.grantSources ? opts.grantSources(target) : ({} as GrantSources),
|
|
71
|
+
);
|
|
72
|
+
const effEmail = (email: string, fallback: boolean) =>
|
|
73
|
+
opts.effectiveEmails && email in opts.effectiveEmails ? opts.effectiveEmails[email] : fallback;
|
|
74
|
+
|
|
75
|
+
const accessControl = {
|
|
76
|
+
canWrite,
|
|
77
|
+
grantSources,
|
|
78
|
+
invalidate: vi.fn(),
|
|
79
|
+
// resolvedView() fans out to these after a successful mutation; harmless stubs.
|
|
80
|
+
// canRead/canDownload/canOwner are ALSO consumed by denyHere's assert.
|
|
81
|
+
canRead: vi.fn(async (_w: string, email: string) => effEmail(email, true)),
|
|
82
|
+
canDownload: vi.fn(async (_w: string, email: string) => effEmail(email, false)),
|
|
83
|
+
canOwner: vi.fn(async (_w: string, email: string) => effEmail(email, false)),
|
|
84
|
+
eligibleWriters: vi.fn(async () => opts.eligibleWriters ?? { roles: [], users: [] }),
|
|
85
|
+
eligibleReaders: vi.fn(async () => ({ restricted: true, roles: [], users: [] })),
|
|
86
|
+
eligibleOwners: vi.fn(async () => ({ roles: [], users: [] })),
|
|
87
|
+
eligibleDownloaders: vi.fn(async () => ({ roles: [], users: [] })),
|
|
88
|
+
} as unknown as IAccessControl;
|
|
89
|
+
|
|
90
|
+
const workspaceService = {
|
|
91
|
+
getOrCreateForBranch: vi.fn(async () => ({ id: WS, name: WS, kbDirName: KB })),
|
|
92
|
+
readFile: vi.fn(async (_id: string, wsRel: string) => {
|
|
93
|
+
const v = files.get(wsRel);
|
|
94
|
+
if (v === undefined) {
|
|
95
|
+
const err = new Error(`ENOENT ${wsRel}`) as NodeJS.ErrnoException;
|
|
96
|
+
err.code = 'ENOENT';
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
return v;
|
|
100
|
+
}),
|
|
101
|
+
writeFile: vi.fn(async (_id: string, wsRel: string, content: string) => {
|
|
102
|
+
files.set(wsRel, content);
|
|
103
|
+
}),
|
|
104
|
+
} as unknown as WorkspaceService;
|
|
105
|
+
|
|
106
|
+
const authService = { getUserById: vi.fn(async () => USER) } as unknown as AuthService;
|
|
107
|
+
|
|
108
|
+
const acquireLock = vi.fn(async () => ({ acquired: true, lock: {} }));
|
|
109
|
+
const releaseLock = vi.fn(async () => undefined);
|
|
110
|
+
const releaseLockNoCommit = vi.fn(async () => undefined);
|
|
111
|
+
const workflowService = {
|
|
112
|
+
getLock: vi.fn(async () => null),
|
|
113
|
+
acquireLock,
|
|
114
|
+
releaseLock,
|
|
115
|
+
releaseLockNoCommit,
|
|
116
|
+
} as unknown as WorkflowService;
|
|
117
|
+
|
|
118
|
+
const eventBus = { emit: vi.fn() } as unknown as WorkflowEventBus;
|
|
119
|
+
const db = {} as unknown as Database;
|
|
120
|
+
|
|
121
|
+
const app = express();
|
|
122
|
+
app.use(express.json());
|
|
123
|
+
app.use('/api', (req, _res, next) => {
|
|
124
|
+
(req as unknown as { userId: string }).userId = USER.id;
|
|
125
|
+
next();
|
|
126
|
+
});
|
|
127
|
+
app.use('/api', createAccessRoutes(accessControl, workspaceService, authService, workflowService, eventBus, db, KB));
|
|
128
|
+
|
|
129
|
+
const server = await new Promise<Server>((resolve) => {
|
|
130
|
+
const s = app.listen(0, () => resolve(s));
|
|
131
|
+
});
|
|
132
|
+
const addr = server.address() as AddressInfo;
|
|
133
|
+
return {
|
|
134
|
+
server,
|
|
135
|
+
baseUrl: `http://127.0.0.1:${addr.port}`,
|
|
136
|
+
stubs: { files, canWrite, grantSources, acquireLock, releaseLock, releaseLockNoCommit },
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function close(s: Server): Promise<void> {
|
|
141
|
+
return new Promise((resolve, reject) => s.close((e) => (e ? reject(e) : resolve())));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const ALICE = { kind: 'user' as const, email: 'alice@bevel.software', displayName: 'Alice' };
|
|
145
|
+
|
|
146
|
+
describe('POST /access/revoke — route classification + modes', () => {
|
|
147
|
+
let h: Harness | null = null;
|
|
148
|
+
afterEach(async () => {
|
|
149
|
+
if (h) await close(h.server);
|
|
150
|
+
h = null;
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const revoke = (body: unknown) =>
|
|
154
|
+
fetch(`${h!.baseUrl}/api/workspace/${encodeURIComponent(WS)}/access/revoke`, {
|
|
155
|
+
method: 'POST',
|
|
156
|
+
headers: { 'Content-Type': 'application/json' },
|
|
157
|
+
body: JSON.stringify(body),
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('default mode — classify the revoke', () => {
|
|
161
|
+
it('DIRECT grant → 200 with the fresh view (target edited)', async () => {
|
|
162
|
+
h = await makeHarness({
|
|
163
|
+
files: { [`${KB}/Sales/access.md`]: '---\nwrite:\n - Alice <alice@bevel.software>\n---\n' },
|
|
164
|
+
grantSources: () => ({}), // after the splice removes her, no source remains
|
|
165
|
+
});
|
|
166
|
+
const res = await revoke({ path: `${KB}/Sales`, kind: 'folder', principal: ALICE });
|
|
167
|
+
expect(res.status).toBe(200);
|
|
168
|
+
// The entry was spliced out of the folder access.md.
|
|
169
|
+
expect(h.stubs.files.get(`${KB}/Sales/access.md`)).not.toContain('alice@bevel.software');
|
|
170
|
+
// The lock was acquired and released (committed), not released-no-commit.
|
|
171
|
+
expect(h.stubs.releaseLock).toHaveBeenCalledOnce();
|
|
172
|
+
expect(h.stubs.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('DIRECT + still inherited → 200 strips the direct entry; the fresh view lists the principal with only the ancestor source', async () => {
|
|
176
|
+
// The file names Alice directly (splice changes it → changed:true), but she
|
|
177
|
+
// is ALSO inherited from Sales. After the direct entry is gone she still
|
|
178
|
+
// resolves, so the fresh view's per-row sources show only the ancestor —
|
|
179
|
+
// which is how the dialog chains into "Remove from parent?" (no special
|
|
180
|
+
// response field; the richer per-verb sources subsume the old stillInherited).
|
|
181
|
+
h = await makeHarness({
|
|
182
|
+
files: {
|
|
183
|
+
[`${KB}/Sales/Deal.md`]:
|
|
184
|
+
'---\nnodeType: process\nwrite:\n - Alice <alice@bevel.software>\n---\n# Deal\n',
|
|
185
|
+
},
|
|
186
|
+
// She remains an effective writer (via the ancestor) so she's still a row...
|
|
187
|
+
eligibleWriters: { roles: [], users: [{ name: 'Alice', email: 'alice@bevel.software' }] },
|
|
188
|
+
// ...and her per-row source is the remaining ancestor.
|
|
189
|
+
grantSources: () => ({ write: [{ kind: 'ancestor', path: 'Sales/access.md' }] }),
|
|
190
|
+
});
|
|
191
|
+
const res = await revoke({ path: `${KB}/Sales/Deal.md`, kind: 'file', principal: ALICE });
|
|
192
|
+
expect(res.status).toBe(200);
|
|
193
|
+
const body = await res.json();
|
|
194
|
+
// The direct entry was spliced out of the node frontmatter...
|
|
195
|
+
expect(h.stubs.files.get(`${KB}/Sales/Deal.md`)).not.toContain('alice@bevel.software');
|
|
196
|
+
// ...and the fresh view exposes her remaining ancestor source (drives the chain).
|
|
197
|
+
expect(body.sources['u:alice@bevel.software'].write).toEqual([
|
|
198
|
+
{ kind: 'ancestor', path: 'Sales/access.md' },
|
|
199
|
+
]);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('DIRECT + NOT otherwise inherited → 200; the principal no longer appears with any source', async () => {
|
|
203
|
+
h = await makeHarness({
|
|
204
|
+
files: { [`${KB}/Sales/access.md`]: '---\nwrite:\n - Alice <alice@bevel.software>\n---\n' },
|
|
205
|
+
grantSources: () => ({}), // nothing left after the direct entry is removed
|
|
206
|
+
});
|
|
207
|
+
const res = await revoke({ path: `${KB}/Sales`, kind: 'folder', principal: ALICE });
|
|
208
|
+
expect(res.status).toBe(200);
|
|
209
|
+
// She's gone from the eligible lists (stubbed empty) so there's no row/source.
|
|
210
|
+
expect((await res.json()).sources['u:alice@bevel.software']).toBeUndefined();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('INHERITED from a file ancestor → 409 { kind: "inherited", sources } and the target is untouched', async () => {
|
|
214
|
+
// The file target names no one; Alice resolves via Sales/access.md.
|
|
215
|
+
h = await makeHarness({
|
|
216
|
+
files: { [`${KB}/Sales/Deal.md`]: '---\nnodeType: process\n---\n# Deal\n' },
|
|
217
|
+
grantSources: () => ({ write: [{ kind: 'ancestor', path: 'Sales/access.md' }] }),
|
|
218
|
+
});
|
|
219
|
+
const res = await revoke({ path: `${KB}/Sales/Deal.md`, kind: 'file', principal: ALICE });
|
|
220
|
+
expect(res.status).toBe(409);
|
|
221
|
+
const body = await res.json();
|
|
222
|
+
expect(body.kind).toBe('inherited');
|
|
223
|
+
expect(body.sources.write).toEqual([{ kind: 'ancestor', path: 'Sales/access.md' }]);
|
|
224
|
+
// The target node frontmatter is unchanged (no deny written).
|
|
225
|
+
expect(h.stubs.files.get(`${KB}/Sales/Deal.md`)).toBe('---\nnodeType: process\n---\n# Deal\n');
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('VERB-SPECIFIC revoke of an INHERITED verb → 409 inherited (unchecking "Can edit" on a direct-download row)', async () => {
|
|
229
|
+
// The exact share-dialog case: the principal is direct on `download` (on the
|
|
230
|
+
// file) but inherits `write` from the parent. Unchecking "Can edit" sends a
|
|
231
|
+
// single-verb revoke of `write` — which finds nothing on the file to splice
|
|
232
|
+
// (changed:false) but still resolves via the ancestor → 409 inherited. The
|
|
233
|
+
// dialog converts this 409 into the "Remove from parent?" flow.
|
|
234
|
+
h = await makeHarness({
|
|
235
|
+
files: {
|
|
236
|
+
[`${KB}/Sales/Deal.md`]:
|
|
237
|
+
'---\nnodeType: process\ndownload:\n - Alice <alice@bevel.software>\n---\n# Deal\n',
|
|
238
|
+
},
|
|
239
|
+
grantSources: () => ({ write: [{ kind: 'ancestor', path: 'Sales/access.md' }] }),
|
|
240
|
+
});
|
|
241
|
+
const res = await revoke({
|
|
242
|
+
path: `${KB}/Sales/Deal.md`,
|
|
243
|
+
kind: 'file',
|
|
244
|
+
principal: ALICE,
|
|
245
|
+
verb: 'write', // ← the single inherited verb being unchecked
|
|
246
|
+
});
|
|
247
|
+
expect(res.status).toBe(409);
|
|
248
|
+
const body = await res.json();
|
|
249
|
+
expect(body.kind).toBe('inherited');
|
|
250
|
+
expect(body.sources.write).toEqual([{ kind: 'ancestor', path: 'Sales/access.md' }]);
|
|
251
|
+
// The direct download entry is untouched — only `write` was targeted.
|
|
252
|
+
expect(h.stubs.files.get(`${KB}/Sales/Deal.md`)).toContain('download:');
|
|
253
|
+
expect(h.stubs.files.get(`${KB}/Sales/Deal.md`)).toContain('alice@bevel.software');
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('NO removable source (resolves only via a group/everyone/rescue) → 200 no-op, NOT a 409', async () => {
|
|
257
|
+
// grantSources is empty (a group/everyone/rescue grant is not a per-user
|
|
258
|
+
// file source), and the target splice no-ops. The route must 200, never 409.
|
|
259
|
+
h = await makeHarness({
|
|
260
|
+
files: { [`${KB}/Sales/Deal.md`]: '---\nnodeType: process\n---\n# Deal\n' },
|
|
261
|
+
grantSources: () => ({}),
|
|
262
|
+
});
|
|
263
|
+
const res = await revoke({ path: `${KB}/Sales/Deal.md`, kind: 'file', principal: ALICE });
|
|
264
|
+
expect(res.status).toBe(200);
|
|
265
|
+
expect(h.stubs.files.get(`${KB}/Sales/Deal.md`)).toBe('---\nnodeType: process\n---\n# Deal\n');
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('NO effective access anywhere → 200 no-op', async () => {
|
|
269
|
+
h = await makeHarness({
|
|
270
|
+
files: { [`${KB}/Sales/access.md`]: '---\nwrite:\n - Admin\n---\n' },
|
|
271
|
+
grantSources: () => ({}),
|
|
272
|
+
});
|
|
273
|
+
const res = await revoke({ path: `${KB}/Sales`, kind: 'folder', principal: ALICE });
|
|
274
|
+
expect(res.status).toBe(200);
|
|
275
|
+
expect(h.stubs.files.get(`${KB}/Sales/access.md`)).toContain('Admin'); // untouched
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('refuses 403 when the caller cannot write the target access config', async () => {
|
|
279
|
+
h = await makeHarness({
|
|
280
|
+
files: { [`${KB}/Sales/access.md`]: '---\nwrite:\n - Alice <alice@bevel.software>\n---\n' },
|
|
281
|
+
canWrite: () => false,
|
|
282
|
+
});
|
|
283
|
+
const res = await revoke({ path: `${KB}/Sales`, kind: 'folder', principal: ALICE });
|
|
284
|
+
expect(res.status).toBe(403);
|
|
285
|
+
// No lock acquired — the gate is BEFORE the lock.
|
|
286
|
+
expect(h.stubs.acquireLock).not.toHaveBeenCalled();
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
describe('mode: remove-from-parent', () => {
|
|
291
|
+
it('gates 403 on the ANCESTOR, not the target (write-target-but-not-ancestor is refused)', async () => {
|
|
292
|
+
// Caller can write the target folder but NOT the ancestor access.md.
|
|
293
|
+
h = await makeHarness({
|
|
294
|
+
files: { [`${KB}/Sales/access.md`]: '---\nwrite:\n - Alice <alice@bevel.software>\n---\n' },
|
|
295
|
+
canWrite: (p) => p !== 'Sales/access.md', // ancestor gate fails
|
|
296
|
+
});
|
|
297
|
+
const res = await revoke({
|
|
298
|
+
path: `${KB}/Sales/Deal.md`,
|
|
299
|
+
kind: 'file',
|
|
300
|
+
principal: ALICE,
|
|
301
|
+
mode: 'remove-from-parent',
|
|
302
|
+
ancestor: 'Sales',
|
|
303
|
+
});
|
|
304
|
+
expect(res.status).toBe(403);
|
|
305
|
+
// The ancestor access.md was never edited.
|
|
306
|
+
expect(h.stubs.files.get(`${KB}/Sales/access.md`)).toContain('alice@bevel.software');
|
|
307
|
+
expect(h.stubs.acquireLock).not.toHaveBeenCalled();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('TOCTOU: aborts 409 stale-ancestor when the principal is no longer inherited from the named ancestor', async () => {
|
|
311
|
+
// The chain changed since the 409 — grantSources no longer reports this
|
|
312
|
+
// ancestor. The route must abort under the lock with kind:'stale-ancestor'
|
|
313
|
+
// and leave the ancestor untouched.
|
|
314
|
+
h = await makeHarness({
|
|
315
|
+
files: { [`${KB}/Sales/access.md`]: '---\nwrite:\n - Admin\n---\n' },
|
|
316
|
+
grantSources: () => ({}), // not inherited from Sales anymore
|
|
317
|
+
});
|
|
318
|
+
const res = await revoke({
|
|
319
|
+
path: `${KB}/Sales/Deal.md`,
|
|
320
|
+
kind: 'file',
|
|
321
|
+
principal: ALICE,
|
|
322
|
+
mode: 'remove-from-parent',
|
|
323
|
+
ancestor: 'Sales',
|
|
324
|
+
});
|
|
325
|
+
expect(res.status).toBe(409);
|
|
326
|
+
expect((await res.json()).kind).toBe('stale-ancestor');
|
|
327
|
+
// Lock acquired (the re-check is under the lock) then released WITHOUT commit.
|
|
328
|
+
expect(h.stubs.acquireLock).toHaveBeenCalledOnce();
|
|
329
|
+
expect(h.stubs.releaseLockNoCommit).toHaveBeenCalledOnce();
|
|
330
|
+
expect(h.stubs.releaseLock).not.toHaveBeenCalled();
|
|
331
|
+
expect(h.stubs.files.get(`${KB}/Sales/access.md`)).toContain('Admin'); // untouched
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it('still-inherited → revokes on the ancestor access.md and 200s', async () => {
|
|
335
|
+
h = await makeHarness({
|
|
336
|
+
files: { [`${KB}/Sales/access.md`]: '---\nwrite:\n - Alice <alice@bevel.software>\n---\n' },
|
|
337
|
+
grantSources: () => ({ write: [{ kind: 'ancestor', path: 'Sales/access.md' }] }),
|
|
338
|
+
});
|
|
339
|
+
const res = await revoke({
|
|
340
|
+
path: `${KB}/Sales/Deal.md`,
|
|
341
|
+
kind: 'file',
|
|
342
|
+
principal: ALICE,
|
|
343
|
+
mode: 'remove-from-parent',
|
|
344
|
+
ancestor: 'Sales',
|
|
345
|
+
});
|
|
346
|
+
expect(res.status).toBe(200);
|
|
347
|
+
// Alice was spliced out of the ANCESTOR access.md.
|
|
348
|
+
expect(h.stubs.files.get(`${KB}/Sales/access.md`)).not.toContain('alice@bevel.software');
|
|
349
|
+
expect(h.stubs.releaseLock).toHaveBeenCalledOnce();
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('requires an ancestor in the body', async () => {
|
|
353
|
+
h = await makeHarness({ files: {} });
|
|
354
|
+
const res = await revoke({
|
|
355
|
+
path: `${KB}/Sales/Deal.md`,
|
|
356
|
+
kind: 'file',
|
|
357
|
+
principal: ALICE,
|
|
358
|
+
mode: 'remove-from-parent',
|
|
359
|
+
});
|
|
360
|
+
expect(res.status).toBe(400);
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
describe('mode: deny-here', () => {
|
|
365
|
+
const BOB = { kind: 'user' as const, email: 'bob@bevel.software', displayName: 'Bob' };
|
|
366
|
+
|
|
367
|
+
it('writes a deny at the target and 200s when the deny removes effective access', async () => {
|
|
368
|
+
// Alice (the actor) can write the target; Bob inherits write from a folder.
|
|
369
|
+
// Deny Bob on the file. denyHere's post-write assert sees Bob no longer has
|
|
370
|
+
// effective access (effectiveEmails), so it commits — 200.
|
|
371
|
+
h = await makeHarness({
|
|
372
|
+
files: { [`${KB}/Sales/Deal.md`]: '---\nnodeType: process\n---\n# Deal\n' },
|
|
373
|
+
effectiveEmails: { 'bob@bevel.software': false },
|
|
374
|
+
});
|
|
375
|
+
const res = await revoke({
|
|
376
|
+
path: `${KB}/Sales/Deal.md`,
|
|
377
|
+
kind: 'file',
|
|
378
|
+
principal: BOB,
|
|
379
|
+
mode: 'deny-here',
|
|
380
|
+
});
|
|
381
|
+
expect(res.status).toBe(200);
|
|
382
|
+
// A deny entry was written into the node's own frontmatter.
|
|
383
|
+
expect(h.stubs.files.get(`${KB}/Sales/Deal.md`)).toContain('deny Bob <bob@bevel.software>');
|
|
384
|
+
expect(h.stubs.releaseLock).toHaveBeenCalledOnce();
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it('rolls back + 409 deny-ineffective when the deny does NOT remove access (e.g. admin-rescue)', async () => {
|
|
388
|
+
// Bob still has effective access after the deny (effectiveEmails true) —
|
|
389
|
+
// denyHere must roll the file back and surface kind:'deny-ineffective'.
|
|
390
|
+
const original = '---\nnodeType: process\n---\n# Deal\n';
|
|
391
|
+
h = await makeHarness({
|
|
392
|
+
files: { [`${KB}/Sales/Deal.md`]: original },
|
|
393
|
+
effectiveEmails: { 'bob@bevel.software': true },
|
|
394
|
+
});
|
|
395
|
+
const res = await revoke({
|
|
396
|
+
path: `${KB}/Sales/Deal.md`,
|
|
397
|
+
kind: 'file',
|
|
398
|
+
principal: BOB,
|
|
399
|
+
mode: 'deny-here',
|
|
400
|
+
});
|
|
401
|
+
expect(res.status).toBe(409);
|
|
402
|
+
expect((await res.json()).kind).toBe('deny-ineffective');
|
|
403
|
+
// Rolled back to the original bytes (no lingering deny entry).
|
|
404
|
+
expect(h.stubs.files.get(`${KB}/Sales/Deal.md`)).toBe(original);
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
});
|