@bevel-software/platform-core-backend 0.22.0 → 0.24.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/dist/core/core-ports.d.ts +11 -1
- package/dist/core/core-ports.d.ts.map +1 -1
- package/dist/core/core-ports.js.map +1 -1
- package/dist/core/create-core-server.d.ts +3 -3
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +46 -13
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +12 -2
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +137 -24
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +46 -1
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +99 -13
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/core-config.d.ts +16 -12
- package/dist/core-config.d.ts.map +1 -1
- package/dist/core-config.js +28 -13
- package/dist/core-config.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +42 -12
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +67 -10
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +214 -35
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +17 -16
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/directory-sync-bot.d.ts.map +1 -1
- package/dist/modules/access/directory-sync-bot.js +7 -3
- package/dist/modules/access/directory-sync-bot.js.map +1 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +8 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.js +8 -2
- package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -1
- package/dist/modules/agent-instructions/compose.d.ts +38 -6
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
- package/dist/modules/agent-instructions/compose.js +39 -6
- package/dist/modules/agent-instructions/compose.js.map +1 -1
- package/dist/modules/agent-instructions/index.d.ts +2 -1
- package/dist/modules/agent-instructions/index.d.ts.map +1 -1
- package/dist/modules/agent-instructions/index.js +2 -1
- package/dist/modules/agent-instructions/index.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts +115 -0
- package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -0
- package/dist/modules/agent-instructions/shared-file-rules.js +272 -0
- package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -0
- package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
- package/dist/modules/audit/agent-audit.service.js +4 -2
- package/dist/modules/audit/agent-audit.service.js.map +1 -1
- package/dist/modules/auth/account-admission.d.ts +58 -7
- package/dist/modules/auth/account-admission.d.ts.map +1 -1
- package/dist/modules/auth/account-admission.js +44 -1
- package/dist/modules/auth/account-admission.js.map +1 -1
- package/dist/modules/auth/account-erasure.service.d.ts.map +1 -1
- package/dist/modules/auth/account-erasure.service.js +57 -16
- package/dist/modules/auth/account-erasure.service.js.map +1 -1
- package/dist/modules/auth/account.routes.d.ts +1 -1
- package/dist/modules/auth/account.routes.d.ts.map +1 -1
- package/dist/modules/auth/account.routes.js +61 -5
- package/dist/modules/auth/account.routes.js.map +1 -1
- package/dist/modules/auth/auth.middleware.d.ts +1 -1
- package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
- package/dist/modules/auth/auth.middleware.js +18 -7
- package/dist/modules/auth/auth.middleware.js.map +1 -1
- package/dist/modules/auth/auth.routes.d.ts.map +1 -1
- package/dist/modules/auth/auth.routes.js +8 -0
- package/dist/modules/auth/auth.routes.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +70 -2
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +197 -18
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.js +7 -2
- package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.d.ts +20 -2
- package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.js +66 -35
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/database/connection.d.ts +16 -0
- package/dist/modules/database/connection.d.ts.map +1 -1
- package/dist/modules/database/connection.js +117 -0
- package/dist/modules/database/connection.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +337 -120
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +116 -58
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/database/migrate.d.ts +8 -0
- package/dist/modules/database/migrate.d.ts.map +1 -1
- package/dist/modules/database/migrate.js +271 -1
- package/dist/modules/database/migrate.js.map +1 -1
- package/dist/modules/kb-fs/branch-name.d.ts.map +1 -1
- package/dist/modules/kb-fs/branch-name.js +12 -2
- package/dist/modules/kb-fs/branch-name.js.map +1 -1
- package/dist/modules/kb-fs/repo-path.d.ts +11 -16
- package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
- package/dist/modules/kb-fs/repo-path.js +79 -0
- package/dist/modules/kb-fs/repo-path.js.map +1 -1
- package/dist/modules/kb-sync/kb-sync.routes.d.ts +2 -2
- package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.routes.js +14 -3
- package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -1
- package/dist/modules/kb-sync/sync-auth.d.ts +3 -1
- package/dist/modules/kb-sync/sync-auth.d.ts.map +1 -1
- package/dist/modules/kb-sync/sync-auth.js +1 -1
- package/dist/modules/kb-sync/sync-auth.js.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +21 -6
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +8 -0
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +38 -8
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +3 -2
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/plugins/join-request-records.store.d.ts.map +1 -1
- package/dist/modules/plugins/join-request-records.store.js +7 -4
- package/dist/modules/plugins/join-request-records.store.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +8 -3
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/settings/setup.routes.d.ts +52 -1
- package/dist/modules/settings/setup.routes.d.ts.map +1 -1
- package/dist/modules/settings/setup.routes.js +207 -19
- package/dist/modules/settings/setup.routes.js.map +1 -1
- package/dist/modules/skills/skills.contract.d.ts +90 -18
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.contract.js +4 -1
- package/dist/modules/skills/skills.contract.js.map +1 -1
- package/dist/modules/skills/skills.service.d.ts +98 -9
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +239 -37
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts +7 -0
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +71 -7
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +12 -4
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.d.ts +3 -3
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts +16 -7
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +34 -12
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts +2 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.js +25 -4
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts +3 -2
- package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
- package/dist/modules/tool-helpers/validate-token.js +1 -1
- package/dist/modules/tool-helpers/validate-token.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +42 -31
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/tool-registry/description-length.d.ts +80 -0
- package/dist/modules/tool-registry/description-length.d.ts.map +1 -0
- package/dist/modules/tool-registry/description-length.js +108 -0
- package/dist/modules/tool-registry/description-length.js.map +1 -0
- package/dist/modules/workflow/agent-tools/change-request-summary.d.ts +98 -0
- package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +1 -0
- package/dist/modules/workflow/agent-tools/change-request-summary.js +81 -0
- package/dist/modules/workflow/agent-tools/change-request-summary.js.map +1 -0
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +75 -35
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/file-lock.service.d.ts +24 -0
- package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
- package/dist/modules/workflow/file-lock.service.js +30 -0
- package/dist/modules/workflow/file-lock.service.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +94 -0
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +193 -3
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.service.d.ts +38 -0
- package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.service.js +60 -1
- package/dist/modules/workflow/pending-commits.service.js.map +1 -1
- package/dist/modules/workflow/recovery-bot.d.ts.map +1 -1
- package/dist/modules/workflow/recovery-bot.js +7 -3
- package/dist/modules/workflow/recovery-bot.js.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js +12 -3
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
- package/dist/modules/workflow/workflow-hooks.d.ts +54 -32
- package/dist/modules/workflow/workflow-hooks.d.ts.map +1 -1
- package/dist/modules/workflow/workflow-hooks.js +16 -1
- package/dist/modules/workflow/workflow-hooks.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +60 -6
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +115 -5
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/agent-access.gate.d.ts +94 -0
- package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -0
- package/dist/modules/workspace/agent-access.gate.js +123 -0
- package/dist/modules/workspace/agent-access.gate.js.map +1 -0
- package/dist/modules/workspace/agent-upload.routes.d.ts +77 -0
- package/dist/modules/workspace/agent-upload.routes.d.ts.map +1 -0
- package/dist/modules/workspace/agent-upload.routes.js +210 -0
- package/dist/modules/workspace/agent-upload.routes.js.map +1 -0
- package/dist/modules/workspace/agent-upload.store.d.ts +284 -0
- package/dist/modules/workspace/agent-upload.store.d.ts.map +1 -0
- package/dist/modules/workspace/agent-upload.store.js +553 -0
- package/dist/modules/workspace/agent-upload.store.js.map +1 -0
- package/dist/modules/workspace/routine-write-policy.d.ts +5 -6
- package/dist/modules/workspace/routine-write-policy.d.ts.map +1 -1
- package/dist/modules/workspace/routine-write-policy.js +5 -6
- package/dist/modules/workspace/routine-write-policy.js.map +1 -1
- package/dist/modules/workspace/session-sink.d.ts +5 -5
- package/dist/modules/workspace/set-aside-clone.d.ts +46 -0
- package/dist/modules/workspace/set-aside-clone.d.ts.map +1 -0
- package/dist/modules/workspace/set-aside-clone.js +92 -0
- package/dist/modules/workspace/set-aside-clone.js.map +1 -0
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts +59 -9
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.js +65 -24
- package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js +3 -3
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.d.ts +40 -0
- package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.js +46 -4
- package/dist/modules/workspace/startup/steps/template-source.js.map +1 -1
- package/dist/modules/workspace/upload-limits.d.ts +13 -0
- package/dist/modules/workspace/upload-limits.d.ts.map +1 -0
- package/dist/modules/workspace/upload-limits.js +13 -0
- package/dist/modules/workspace/upload-limits.js.map +1 -0
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +93 -4
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +128 -5
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +316 -58
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +13 -11
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +791 -198
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/modules/workspace/write-denial.d.ts +0 -6
- package/dist/modules/workspace/write-denial.d.ts.map +1 -1
- package/dist/modules/workspace/write-denial.js +0 -6
- package/dist/modules/workspace/write-denial.js.map +1 -1
- package/dist/modules/workspace/zip-entry-rules.d.ts +114 -0
- package/dist/modules/workspace/zip-entry-rules.d.ts.map +1 -0
- package/dist/modules/workspace/zip-entry-rules.js +154 -0
- package/dist/modules/workspace/zip-entry-rules.js.map +1 -0
- package/dist/modules/write-access/write-access.d.ts +60 -0
- package/dist/modules/write-access/write-access.d.ts.map +1 -0
- package/dist/modules/write-access/write-access.js +129 -0
- package/dist/modules/write-access/write-access.js.map +1 -0
- package/dist/shared/column-crypto.d.ts +194 -0
- package/dist/shared/column-crypto.d.ts.map +1 -0
- package/dist/shared/column-crypto.js +144 -0
- package/dist/shared/column-crypto.js.map +1 -0
- package/dist/shared/domain-errors.d.ts +25 -0
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +28 -0
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/git.contract.d.ts +20 -0
- package/dist/shared/git.contract.d.ts.map +1 -1
- package/dist/shared/git.contract.js +26 -0
- package/dist/shared/git.contract.js.map +1 -1
- package/dist/shared/token-crypto.d.ts.map +1 -1
- package/dist/shared/token-crypto.js +25 -1
- package/dist/shared/token-crypto.js.map +1 -1
- package/dist/tenancy/static-tenant-source.d.ts +0 -1
- package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
- package/dist/tenancy/static-tenant-source.js +1 -2
- package/dist/tenancy/static-tenant-source.js.map +1 -1
- package/dist/tenancy/tenant-secrets.d.ts +5 -1
- package/dist/tenancy/tenant-secrets.d.ts.map +1 -1
- package/dist/tenancy/tenant-secrets.js +4 -0
- package/dist/tenancy/tenant-secrets.js.map +1 -1
- package/kb-template/AGENTS.md +2 -0
- package/migrations/0014_change_request_closed_reason.sql +1 -0
- package/migrations/0015_account_deactivation.sql +3 -0
- package/migrations/0016_pii_encryption.sql +20 -0
- package/migrations/meta/0014_snapshot.json +2265 -0
- package/migrations/meta/0015_snapshot.json +2271 -0
- package/migrations/meta/0016_snapshot.json +2327 -0
- package/migrations/meta/_journal.json +21 -0
- package/package.json +3 -3
- package/src/__tests__/kb-layout-config.test.ts +0 -2
- package/src/__tests__/retired-settings.test.ts +96 -0
- package/src/core/__tests__/gated-boot.test.ts +132 -0
- package/src/core/__tests__/lifecycle.test.ts +241 -5
- package/src/core/__tests__/set-aside-root-is-one-place.test.ts +63 -0
- package/src/core/core-ports.ts +11 -1
- package/src/core/create-core-server.ts +52 -16
- package/src/core/create-core-services.ts +156 -24
- package/src/core/lifecycle.ts +117 -13
- package/src/core-config.ts +31 -14
- package/src/index.ts +30 -1
- package/src/modules/access/__tests__/access-control.preview-relocation.test.ts +385 -0
- package/src/modules/access/__tests__/access-control.prospective.test.ts +94 -18
- package/src/modules/access/__tests__/access.routes.prospective.test.ts +6 -7
- package/src/modules/access/__tests__/users-db-double.ts +21 -12
- package/src/modules/access/access-control.interface.ts +43 -12
- package/src/modules/access/access-control.service.ts +234 -36
- package/src/modules/access/access.routes.ts +17 -16
- package/src/modules/access/directory-sync-bot.ts +7 -3
- package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +11 -5
- package/src/modules/agent-instructions/__tests__/compose.test.ts +19 -10
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +238 -0
- package/src/modules/agent-instructions/agent-instructions.routes.ts +12 -2
- package/src/modules/agent-instructions/compose.ts +50 -7
- package/src/modules/agent-instructions/index.ts +12 -0
- package/src/modules/agent-instructions/shared-file-rules.ts +314 -0
- package/src/modules/audit/agent-audit.service.ts +4 -2
- package/src/modules/auth/__tests__/account-deactivation.test.ts +253 -0
- package/src/modules/auth/__tests__/account-erasure.approval-gate.test.ts +6 -2
- package/src/modules/auth/__tests__/account.routes.test.ts +87 -6
- package/src/modules/auth/__tests__/auth.middleware.test.ts +45 -22
- package/src/modules/auth/__tests__/auth.service.test.ts +4 -2
- package/src/modules/auth/account-admission.ts +78 -9
- package/src/modules/auth/account-erasure.service.ts +69 -18
- package/src/modules/auth/account.routes.ts +63 -7
- package/src/modules/auth/auth.middleware.ts +19 -8
- package/src/modules/auth/auth.routes.ts +8 -0
- package/src/modules/auth/auth.service.ts +206 -23
- package/src/modules/auth/oidc-auth-provider.ts +7 -2
- package/src/modules/code-mode/__tests__/chain-runtime.e2e.test.ts +335 -0
- package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +46 -2
- package/src/modules/code-mode/code-mode.tool.ts +80 -34
- package/src/modules/database/__tests__/connection.test.ts +12 -0
- package/src/modules/database/__tests__/pii-backfill.pg.test.ts +561 -0
- package/src/modules/database/connection.ts +117 -0
- package/src/modules/database/core-schema.ts +116 -58
- package/src/modules/database/migrate.ts +353 -1
- package/src/modules/kb-fs/__tests__/branch-name.test.ts +10 -0
- package/src/modules/kb-fs/__tests__/repo-path.test.ts +123 -0
- package/src/modules/kb-fs/branch-name.ts +14 -1
- package/src/modules/kb-fs/repo-path.ts +85 -0
- package/src/modules/kb-sync/__tests__/kb-sync.routes.test.ts +26 -1
- package/src/modules/kb-sync/kb-sync.routes.ts +14 -4
- package/src/modules/kb-sync/sync-auth.ts +2 -2
- package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +60 -3
- package/src/modules/mcp/__tests__/mcp.e2e.test.ts +4 -3
- package/src/modules/mcp/__tests__/mcp.service.test.ts +115 -13
- package/src/modules/mcp/mcp-auth.middleware.ts +20 -6
- package/src/modules/mcp/mcp.service.ts +46 -7
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +3 -1
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +2 -1
- package/src/modules/plugins/join-request-records.store.ts +7 -4
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +16 -0
- package/src/modules/settings/__tests__/setup.routes.git-mode.test.ts +91 -58
- package/src/modules/settings/__tests__/setup.routes.github-app.test.ts +25 -8
- package/src/modules/settings/__tests__/setup.routes.managed-phase.test.ts +14 -11
- package/src/modules/settings/__tests__/setup.routes.repository-change.test.ts +390 -0
- package/src/modules/settings/__tests__/setup.routes.test.ts +3 -0
- package/src/modules/settings/deployment-settings.service.ts +8 -3
- package/src/modules/settings/setup.routes.ts +263 -19
- package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +2 -1
- package/src/modules/skills/__tests__/branch-skills.tools.test.ts +218 -0
- package/src/modules/skills/__tests__/skills.service.test.ts +229 -5
- package/src/modules/skills/skills.contract.ts +91 -18
- package/src/modules/skills/skills.service.ts +278 -41
- package/src/modules/skills/skills.tools.ts +80 -8
- package/src/modules/tool-auth/__tests__/manual-auth.middleware.test.ts +14 -1
- package/src/modules/tool-auth/external-api-key.service.ts +12 -4
- package/src/modules/tool-auth/internal-token.service.ts +3 -3
- package/src/modules/tool-auth/tool-auth.middleware.ts +34 -11
- package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +5 -3
- package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +3 -3
- package/src/modules/tool-helpers/__tests__/validate-token.test.ts +11 -1
- package/src/modules/tool-helpers/tool-handler.ts +24 -4
- package/src/modules/tool-helpers/tool.contract.ts +3 -2
- package/src/modules/tool-helpers/validate-token.ts +1 -1
- package/src/modules/tool-manuals/tool-manuals.tools.ts +44 -31
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +378 -0
- package/src/modules/tool-registry/description-length.ts +111 -0
- package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +111 -0
- package/src/modules/workflow/__tests__/workflow.routes.history-read-gate.test.ts +25 -0
- package/src/modules/workflow/__tests__/workflow.service.repository-replaced.test.ts +249 -0
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +264 -6
- package/src/modules/workflow/agent-tools/change-request-summary.ts +182 -0
- package/src/modules/workflow/agent-tools/workflow.tools.ts +86 -35
- package/src/modules/workflow/file-lock.service.ts +31 -0
- package/src/modules/workflow/git/__tests__/git.service.fileBytesAtCommit.test.ts +260 -0
- package/src/modules/workflow/git/__tests__/git.service.prFetchFailure.test.ts +170 -0
- package/src/modules/workflow/git/git.service.ts +215 -1
- package/src/modules/workflow/pending-commits.service.ts +64 -2
- package/src/modules/workflow/recovery-bot.ts +7 -3
- package/src/modules/workflow/review-workflow/__tests__/carry-approvals-forward.test.ts +4 -0
- package/src/modules/workflow/review-workflow/__tests__/erase-approver.test.ts +25 -3
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +12 -3
- package/src/modules/workflow/workflow-hooks.ts +64 -26
- package/src/modules/workflow/workflow.service.ts +125 -5
- package/src/modules/workspace/__tests__/agent-access.gate.test.ts +208 -0
- package/src/modules/workspace/__tests__/agent-uploads.test.ts +1604 -0
- package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +394 -0
- package/src/modules/workspace/__tests__/file-stat-access.test.ts +2 -1
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +2 -1
- package/src/modules/workspace/__tests__/set-aside-clone.test.ts +52 -0
- package/src/modules/workspace/__tests__/workspace.routes.at-ref.test.ts +305 -0
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +2 -0
- package/src/modules/workspace/__tests__/workspace.service.any-workspace-credentials.test.ts +156 -0
- package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +147 -0
- package/src/modules/workspace/__tests__/workspace.service.replaced-repository.test.ts +402 -0
- package/src/modules/workspace/__tests__/workspace.service.test.ts +77 -9
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +41 -39
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +2 -3
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +766 -56
- package/src/modules/workspace/agent-access.gate.ts +164 -0
- package/src/modules/workspace/agent-upload.routes.ts +214 -0
- package/src/modules/workspace/agent-upload.store.ts +668 -0
- package/src/modules/workspace/routine-write-policy.ts +5 -6
- package/src/modules/workspace/session-sink.ts +5 -5
- package/src/modules/workspace/set-aside-clone.ts +96 -0
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +86 -0
- package/src/modules/workspace/startup/kb-startup-runner.ts +115 -34
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +9 -9
- package/src/modules/workspace/startup/steps/seed-tree.ts +3 -6
- package/src/modules/workspace/startup/steps/template-source.ts +53 -5
- package/src/modules/workspace/upload-limits.ts +12 -0
- package/src/modules/workspace/workspace.routes.ts +96 -5
- package/src/modules/workspace/workspace.service.ts +319 -61
- package/src/modules/workspace/workspace.tools.ts +894 -214
- package/src/modules/workspace/write-denial.ts +0 -8
- package/src/modules/workspace/zip-entry-rules.ts +173 -0
- package/src/modules/write-access/__tests__/write-access.test.ts +248 -0
- package/src/modules/write-access/write-access.ts +153 -0
- package/src/shared/__tests__/column-crypto.test.ts +217 -0
- package/src/shared/column-crypto.ts +218 -0
- package/src/shared/domain-errors.ts +31 -0
- package/src/shared/git.contract.ts +27 -0
- package/src/shared/token-crypto.ts +28 -1
- package/src/tenancy/__tests__/static-tenant-source.test.ts +4 -1
- package/src/tenancy/static-tenant-source.ts +1 -3
- package/src/tenancy/tenant-secrets.ts +5 -1
- package/dist/modules/workflow/session-ontology.policy.d.ts +0 -52
- package/dist/modules/workflow/session-ontology.policy.d.ts.map +0 -1
- package/dist/modules/workflow/session-ontology.policy.js +0 -62
- package/dist/modules/workflow/session-ontology.policy.js.map +0 -1
- package/dist/modules/workflow/session-ontology.service.d.ts +0 -105
- package/dist/modules/workflow/session-ontology.service.d.ts.map +0 -1
- package/dist/modules/workflow/session-ontology.service.js +0 -147
- package/dist/modules/workflow/session-ontology.service.js.map +0 -1
- package/dist/modules/workspace/session-ontology.gate.d.ts +0 -114
- package/dist/modules/workspace/session-ontology.gate.d.ts.map +0 -1
- package/dist/modules/workspace/session-ontology.gate.js +0 -161
- package/dist/modules/workspace/session-ontology.gate.js.map +0 -1
- package/dist/shared/kb-layout.d.ts +0 -39
- package/dist/shared/kb-layout.d.ts.map +0 -1
- package/dist/shared/kb-layout.js +0 -103
- package/dist/shared/kb-layout.js.map +0 -1
- package/dist/shared/kb-layout.test.d.ts +0 -2
- package/dist/shared/kb-layout.test.d.ts.map +0 -1
- package/dist/shared/kb-layout.test.js +0 -75
- package/dist/shared/kb-layout.test.js.map +0 -1
- package/src/modules/workflow/__tests__/session-ontology.policy.test.ts +0 -62
- package/src/modules/workflow/__tests__/session-ontology.service.test.ts +0 -201
- package/src/modules/workflow/session-ontology.policy.ts +0 -70
- package/src/modules/workflow/session-ontology.service.ts +0 -183
- package/src/modules/workspace/__tests__/session-ontology.gate.test.ts +0 -239
- package/src/modules/workspace/session-ontology.gate.ts +0 -191
- package/src/shared/kb-layout.test.ts +0 -98
- package/src/shared/kb-layout.ts +0 -102
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { testKbContext } from '../../../__tests__/kb-context.js';
|
|
3
|
+
import type { GitService } from '../git/git.service.js';
|
|
4
|
+
import type { PullRequestService } from '../git/pull-request.service.js';
|
|
5
|
+
import type { IReviewWorkflowService } from '../review-workflow/review-workflow.interface.js';
|
|
6
|
+
import type { WorkspaceService } from '../../workspace/workspace.service.js';
|
|
7
|
+
import type { IAccessControl } from '../../access/access-control.interface.js';
|
|
8
|
+
import type { FileLockService } from '../file-lock.service.js';
|
|
9
|
+
import type { PendingCommitsService } from '../pending-commits.service.js';
|
|
10
|
+
import type { Database } from '../../database/connection.js';
|
|
11
|
+
import { WorkflowService } from '../workflow.service.js';
|
|
12
|
+
import { openChangeGate } from '../../../__tests__/open-change-gate.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The CLOSE half of the admin's choice when the knowledge-base repository is
|
|
16
|
+
* replaced: the requests point at branches that are not in the new
|
|
17
|
+
* repository, so they are closed — with a reason, releasing the locks held on
|
|
18
|
+
* those branches and taking any commit still queued for them off the worker,
|
|
19
|
+
* and without deleting a single row.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
interface ClosedRow {
|
|
23
|
+
number: number;
|
|
24
|
+
sourceBranch: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* DB stub shaped like the real close: ONE `update … where state = 'open'
|
|
29
|
+
* returning` per pass, answering with the rows that pass actually closed.
|
|
30
|
+
*
|
|
31
|
+
* `passes` is therefore what the statement RETURNS, pass by pass — which is
|
|
32
|
+
* how a request already closed by a merge or a withdrawal is expressed (it is
|
|
33
|
+
* simply not in the returned set) and how a request opened BETWEEN passes is
|
|
34
|
+
* expressed (it turns up in a later one). A pass past the end answers nothing,
|
|
35
|
+
* which is what stops the loop.
|
|
36
|
+
*/
|
|
37
|
+
function makeDb(passes: ClosedRow[][], sets: Record<string, unknown>[], openCount: number) {
|
|
38
|
+
let pass = 0;
|
|
39
|
+
const db = {
|
|
40
|
+
// The count is a SQL aggregate: one row, one integer.
|
|
41
|
+
select: () => ({ from: () => ({ where: async () => [{ open: openCount }] }) }),
|
|
42
|
+
update: () => ({
|
|
43
|
+
set: (values: Record<string, unknown>) => {
|
|
44
|
+
sets.push(values);
|
|
45
|
+
return { where: () => ({ returning: async () => passes[pass++] ?? [] }) };
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
// Nothing here may ever delete a change request.
|
|
49
|
+
delete: () => {
|
|
50
|
+
throw new Error('a change request must never be deleted');
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
return db as unknown as Database;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function makeService(passes: ClosedRow[][], openCount = passes.flat().length) {
|
|
57
|
+
const sets: Record<string, unknown>[] = [];
|
|
58
|
+
const unlocked: string[] = [];
|
|
59
|
+
const dequeued: string[] = [];
|
|
60
|
+
const invalidated: number[] = [];
|
|
61
|
+
const emitted: unknown[] = [];
|
|
62
|
+
const fileLocks = {
|
|
63
|
+
releaseAllOnBranch: vi.fn(async (branch: string) => {
|
|
64
|
+
unlocked.push(branch);
|
|
65
|
+
return 2;
|
|
66
|
+
}),
|
|
67
|
+
} as unknown as FileLockService;
|
|
68
|
+
const pendingCommits = {
|
|
69
|
+
markNeedsAttentionOnBranch: vi.fn(async (branch: string) => {
|
|
70
|
+
dequeued.push(branch);
|
|
71
|
+
return 1;
|
|
72
|
+
}),
|
|
73
|
+
} as unknown as PendingCommitsService;
|
|
74
|
+
const prs = {
|
|
75
|
+
invalidateDetailCache: vi.fn((n: number) => invalidated.push(n)),
|
|
76
|
+
} as unknown as PullRequestService;
|
|
77
|
+
const svc = new WorkflowService(
|
|
78
|
+
makeDb(passes, sets, openCount),
|
|
79
|
+
{} as GitService,
|
|
80
|
+
prs,
|
|
81
|
+
{} as IReviewWorkflowService,
|
|
82
|
+
{} as WorkspaceService,
|
|
83
|
+
{} as IAccessControl,
|
|
84
|
+
fileLocks,
|
|
85
|
+
pendingCommits,
|
|
86
|
+
testKbContext(),
|
|
87
|
+
openChangeGate(),
|
|
88
|
+
);
|
|
89
|
+
(svc as unknown as { events?: { emit(e: unknown): void } }).events = {
|
|
90
|
+
emit: (e: unknown) => emitted.push(e),
|
|
91
|
+
};
|
|
92
|
+
return { svc, sets, unlocked, dequeued, invalidated, emitted, fileLocks, pendingCommits };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
describe('closeOpenChangeRequestsAsRepositoryReplaced', () => {
|
|
96
|
+
it('closes every open request with the reason, and deletes nothing', async () => {
|
|
97
|
+
const { svc, sets, invalidated, emitted } = makeService([
|
|
98
|
+
[
|
|
99
|
+
{ number: 7, sourceBranch: 'alice/draft' },
|
|
100
|
+
{ number: 9, sourceBranch: 'bob/draft' },
|
|
101
|
+
],
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
await expect(svc.closeOpenChangeRequestsAsRepositoryReplaced()).resolves.toBe(2);
|
|
105
|
+
|
|
106
|
+
// ONE statement closed both: a read-then-write loop would have missed any
|
|
107
|
+
// request opened while it ran.
|
|
108
|
+
expect(sets).toHaveLength(2); // the closing pass, then the pass that found nothing
|
|
109
|
+
expect(sets[0]?.state).toBe('closed');
|
|
110
|
+
// The reason outlives the admin who chose it — that is the whole point
|
|
111
|
+
// of a column rather than a log line.
|
|
112
|
+
expect(sets[0]?.closedReason).toBe('repository-replaced');
|
|
113
|
+
expect(sets[0]?.closedAt).toBeInstanceOf(Date);
|
|
114
|
+
expect(invalidated).toEqual([7, 9]);
|
|
115
|
+
expect(emitted).toEqual([
|
|
116
|
+
{ kind: 'change-request-rejected', number: 7 },
|
|
117
|
+
{ kind: 'change-request-rejected', number: 9 },
|
|
118
|
+
]);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('closes a request opened while it was closing the others', async () => {
|
|
122
|
+
// The deployment is still live — the address has not been saved yet — so
|
|
123
|
+
// `POST /change-requests` is still being served. The first pass closed two;
|
|
124
|
+
// #11 arrived after it and is closed by the next one.
|
|
125
|
+
const { svc, invalidated } = makeService([
|
|
126
|
+
[
|
|
127
|
+
{ number: 7, sourceBranch: 'alice/draft' },
|
|
128
|
+
{ number: 9, sourceBranch: 'bob/draft' },
|
|
129
|
+
],
|
|
130
|
+
[{ number: 11, sourceBranch: 'carol/draft' }],
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
await expect(svc.closeOpenChangeRequestsAsRepositoryReplaced()).resolves.toBe(3);
|
|
134
|
+
expect(invalidated).toEqual([7, 9, 11]);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('gives up after a bounded number of passes rather than holding the save open', async () => {
|
|
138
|
+
// A client opening requests in a loop must not be able to keep the save
|
|
139
|
+
// from ever returning. What survives the last pass was opened after the
|
|
140
|
+
// repository was replaced, and the deleted-branch sweep owns it.
|
|
141
|
+
const endless = Array.from({ length: 50 }, (_, i) => [
|
|
142
|
+
{ number: i + 1, sourceBranch: `writer-${i}/draft` },
|
|
143
|
+
]);
|
|
144
|
+
const { svc } = makeService(endless, 50);
|
|
145
|
+
const closed = await svc.closeOpenChangeRequestsAsRepositoryReplaced();
|
|
146
|
+
expect(closed).toBeGreaterThan(0);
|
|
147
|
+
expect(closed).toBeLessThan(50);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('skips a request a merge or a withdrawal closed first', async () => {
|
|
151
|
+
// The guard is the statement's own `where state = 'open'`: #9 was closed by
|
|
152
|
+
// somebody else between the count and this call, so it is not in the
|
|
153
|
+
// returned set — and nothing is done on its behalf.
|
|
154
|
+
const { svc, unlocked, dequeued, invalidated, emitted } = makeService(
|
|
155
|
+
[[{ number: 7, sourceBranch: 'alice/draft' }]],
|
|
156
|
+
2,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
await expect(svc.closeOpenChangeRequestsAsRepositoryReplaced()).resolves.toBe(1);
|
|
160
|
+
|
|
161
|
+
expect(unlocked).toEqual(['alice/draft']);
|
|
162
|
+
expect(dequeued).toEqual(['alice/draft']);
|
|
163
|
+
expect(invalidated).toEqual([7]);
|
|
164
|
+
expect(emitted).toEqual([{ kind: 'change-request-rejected', number: 7 }]);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('releases the file locks held on the branches it closes', async () => {
|
|
168
|
+
const { svc, unlocked } = makeService([
|
|
169
|
+
[
|
|
170
|
+
{ number: 7, sourceBranch: 'alice/draft' },
|
|
171
|
+
{ number: 9, sourceBranch: 'bob/draft' },
|
|
172
|
+
],
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
await svc.closeOpenChangeRequestsAsRepositoryReplaced();
|
|
176
|
+
|
|
177
|
+
// Nobody can publish those bytes or release the lock by finishing: the
|
|
178
|
+
// branch belongs to a repository this deployment no longer has.
|
|
179
|
+
expect(unlocked).toEqual(['alice/draft', 'bob/draft']);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('takes the commits already queued for those branches off the worker', async () => {
|
|
183
|
+
const { svc, dequeued, pendingCommits } = makeService([
|
|
184
|
+
[{ number: 7, sourceBranch: 'alice/draft' }],
|
|
185
|
+
]);
|
|
186
|
+
|
|
187
|
+
await svc.closeOpenChangeRequestsAsRepositoryReplaced();
|
|
188
|
+
|
|
189
|
+
// A release ENQUEUES the bytes and only then drops its lock, so dropping
|
|
190
|
+
// the locks can leave rows waiting. Left pending, the worker would commit
|
|
191
|
+
// them onto a same-named branch of a different repository after the
|
|
192
|
+
// re-clone.
|
|
193
|
+
expect(dequeued).toEqual(['alice/draft']);
|
|
194
|
+
expect(vi.mocked(pendingCommits.markNeedsAttentionOnBranch).mock.calls[0]?.[1]).toMatch(
|
|
195
|
+
/repository was replaced/,
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('still closes the requests when a lock release fails', async () => {
|
|
200
|
+
const { svc, fileLocks } = makeService([[{ number: 7, sourceBranch: 'alice/draft' }]]);
|
|
201
|
+
vi.mocked(fileLocks.releaseAllOnBranch).mockRejectedValueOnce(new Error('lock table is down'));
|
|
202
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
203
|
+
try {
|
|
204
|
+
// The close is the decision the admin made; a lock expires on its own
|
|
205
|
+
// within the minute, so it is not worth half-closing the set over.
|
|
206
|
+
await expect(svc.closeOpenChangeRequestsAsRepositoryReplaced()).resolves.toBe(1);
|
|
207
|
+
} finally {
|
|
208
|
+
vi.restoreAllMocks();
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('still closes the requests when the queued commits cannot be set aside', async () => {
|
|
213
|
+
const { svc, pendingCommits, unlocked } = makeService([
|
|
214
|
+
[{ number: 7, sourceBranch: 'alice/draft' }],
|
|
215
|
+
]);
|
|
216
|
+
vi.mocked(pendingCommits.markNeedsAttentionOnBranch).mockRejectedValueOnce(
|
|
217
|
+
new Error('pending_commits is down'),
|
|
218
|
+
);
|
|
219
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
220
|
+
try {
|
|
221
|
+
await expect(svc.closeOpenChangeRequestsAsRepositoryReplaced()).resolves.toBe(1);
|
|
222
|
+
expect(unlocked).toEqual(['alice/draft']);
|
|
223
|
+
} finally {
|
|
224
|
+
vi.restoreAllMocks();
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('closes nothing, and asks nothing of the lock table, when no request is open', async () => {
|
|
229
|
+
const { svc, unlocked, dequeued, emitted } = makeService([]);
|
|
230
|
+
await expect(svc.closeOpenChangeRequestsAsRepositoryReplaced()).resolves.toBe(0);
|
|
231
|
+
expect(unlocked).toEqual([]);
|
|
232
|
+
expect(dequeued).toEqual([]);
|
|
233
|
+
expect(emitted).toEqual([]);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
describe('countOpenChangeRequests', () => {
|
|
238
|
+
it('answers how many are open, so the confirmation can say what it is about', async () => {
|
|
239
|
+
const { svc } = makeService([], 2);
|
|
240
|
+
// A SQL aggregate: the question is an integer, and materializing one row
|
|
241
|
+
// per open request to answer it would scale with the wrong thing.
|
|
242
|
+
await expect(svc.countOpenChangeRequests()).resolves.toBe(2);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('answers zero when the aggregate comes back empty', async () => {
|
|
246
|
+
const { svc } = makeService([], 0);
|
|
247
|
+
await expect(svc.countOpenChangeRequests()).resolves.toBe(0);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
@@ -9,11 +9,76 @@ import { createToolContextResolver } from '../../../tool-helpers/tool-context.js
|
|
|
9
9
|
import { createToolHandlerFactory } from '../../../tool-helpers/tool-handler.js';
|
|
10
10
|
import { createManualRoutes } from '../../../tool-registry/manual.routes.js';
|
|
11
11
|
import { registerWorkflowTools } from '../workflow.tools.js';
|
|
12
|
-
import { OpenChangeRequestBlocksMergeError } from '../../../../shared/domain-errors.js';
|
|
12
|
+
import { ChangeRequestConflictsError, OpenChangeRequestBlocksMergeError } from '../../../../shared/domain-errors.js';
|
|
13
13
|
|
|
14
14
|
const WS = 'target-company-state';
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* One file of a change-request detail as the service returns it: a 15 KB
|
|
18
|
+
* addition, patch included. The reported failure was 27 of these — the detail
|
|
19
|
+
* answered ~445,000 characters and overflowed the agent's tool-result limit.
|
|
20
|
+
*/
|
|
21
|
+
function addedFile(i: number): Record<string, unknown> {
|
|
22
|
+
const path = `KnowledgeBase/Engineering/Knowledge/Hexis/Generated-Node-${i}.md`;
|
|
23
|
+
return {
|
|
24
|
+
path,
|
|
25
|
+
status: 'added',
|
|
26
|
+
additions: 400,
|
|
27
|
+
deletions: 0,
|
|
28
|
+
// 400 × 37 characters ≈ 15 KB, as reported.
|
|
29
|
+
patch: `@@ -0,0 +1,400 @@\n${'+a line of generated knowledge text\n'.repeat(400)}`,
|
|
30
|
+
isBinary: false,
|
|
31
|
+
sha: `blob-${i}`,
|
|
32
|
+
rawUrl: `https://raw.example.com/blob-${i}`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The per-file approval state the detail carries, as the app's dialog reads it. */
|
|
37
|
+
function approvalOf(path: string): Record<string, unknown> {
|
|
38
|
+
return {
|
|
39
|
+
path,
|
|
40
|
+
eligibleApprovers: {
|
|
41
|
+
roles: ['Admin', 'Engineering'],
|
|
42
|
+
users: [{ name: 'Razvan Radulescu', email: 'razvan.radulescu@bevel.software' }],
|
|
43
|
+
},
|
|
44
|
+
approvedBy: [],
|
|
45
|
+
eligibilityResolved: true,
|
|
46
|
+
isApproved: false,
|
|
47
|
+
inMergeGate: true,
|
|
48
|
+
viewerCanApprove: true,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** A full change-request detail over the given files — what `openChangeRequest` answers. */
|
|
53
|
+
function detailOf(files: Record<string, unknown>[]): Record<string, unknown> {
|
|
54
|
+
return {
|
|
55
|
+
number: 7,
|
|
56
|
+
url: 'https://bevel.example.com/change-requests/7',
|
|
57
|
+
title: 'My change',
|
|
58
|
+
state: 'open',
|
|
59
|
+
branch: 'me/draft',
|
|
60
|
+
base: 'target-company-state',
|
|
61
|
+
body: 'Body shown verbatim to reviewers.',
|
|
62
|
+
author: { login: 'bevel-bot' },
|
|
63
|
+
headSha: 'head-1',
|
|
64
|
+
baseSha: 'base-1',
|
|
65
|
+
files,
|
|
66
|
+
comments: [],
|
|
67
|
+
approvals: files.map((f) => approvalOf(f.path as string)),
|
|
68
|
+
mergeableInBevel: false,
|
|
69
|
+
mergeBlockedReasons: files.map((f) => `${f.path as string} is not approved`),
|
|
70
|
+
mergeWarnings: files.map((f) => `${f.path as string} is not approved`),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The reported case: 27 new files of 15 KB each. */
|
|
75
|
+
const BIG = () => detailOf(Array.from({ length: 27 }, (_, i) => addedFile(i)));
|
|
76
|
+
|
|
16
77
|
let calls: unknown[][] = [];
|
|
78
|
+
/** What the service answers `openChangeRequest` with; a test replaces it. */
|
|
79
|
+
let opened: Record<string, unknown> = BIG();
|
|
80
|
+
/** Set to make `openChangeRequest` throw instead. */
|
|
81
|
+
let openFails: Error | undefined;
|
|
17
82
|
const externalApiKeyService = {
|
|
18
83
|
looksLikeExternalApiKey: (t: string) => typeof t === 'string' && t.startsWith('bevel_'),
|
|
19
84
|
verifyAndLoadToken: async (t: string) =>
|
|
@@ -38,7 +103,8 @@ const workflowService = {
|
|
|
38
103
|
},
|
|
39
104
|
openChangeRequest: async (ws: string, user: { id: string }, body: unknown) => {
|
|
40
105
|
calls.push(['openChangeRequest', ws, user.id, body]);
|
|
41
|
-
|
|
106
|
+
if (openFails) throw openFails;
|
|
107
|
+
return opened;
|
|
42
108
|
},
|
|
43
109
|
createBranch: async (ws: string, name: string) => {
|
|
44
110
|
calls.push(['createBranch', ws, name]);
|
|
@@ -114,6 +180,8 @@ const post = (url: string, bearer: string, body: unknown = {}) =>
|
|
|
114
180
|
|
|
115
181
|
beforeEach(() => {
|
|
116
182
|
calls = [];
|
|
183
|
+
opened = BIG();
|
|
184
|
+
openFails = undefined;
|
|
117
185
|
});
|
|
118
186
|
afterEach(async () => {
|
|
119
187
|
if (httpServer) await new Promise<void>((r) => httpServer!.close(() => r()));
|
|
@@ -229,7 +297,7 @@ describe('registerWorkflowTools', () => {
|
|
|
229
297
|
expect(calls).toContainEqual(['listBranches', 'existing-ws']);
|
|
230
298
|
});
|
|
231
299
|
|
|
232
|
-
it('open_change_request
|
|
300
|
+
it('open_change_request acts on the SOURCE branch\'s workspace', async () => {
|
|
233
301
|
const base = await start();
|
|
234
302
|
const res = await post(`${base}/api/agent/tools/open_change_request`, writeTok(), {
|
|
235
303
|
sourceBranch: 'me/draft',
|
|
@@ -237,9 +305,6 @@ describe('registerWorkflowTools', () => {
|
|
|
237
305
|
title: 'My change',
|
|
238
306
|
});
|
|
239
307
|
expect(res.status).toBe(200);
|
|
240
|
-
expect(await res.json()).toMatchObject({
|
|
241
|
-
changeRequest: { number: 7, url: 'https://bevel.example.com/change-requests/7' },
|
|
242
|
-
});
|
|
243
308
|
// The workspace must be derived from the SOURCE branch (not a separate
|
|
244
309
|
// `branch` arg) — encodeURIComponent('me/draft'). Regression guard for the
|
|
245
310
|
// `-b undefined` clone bug when the model omitted `branch`.
|
|
@@ -251,6 +316,199 @@ describe('registerWorkflowTools', () => {
|
|
|
251
316
|
]);
|
|
252
317
|
});
|
|
253
318
|
|
|
319
|
+
/**
|
|
320
|
+
* One test per Scenario of the Specification
|
|
321
|
+
* (`hx-change-request-answer-is-small-spec`). The reported failure: opening a
|
|
322
|
+
* request for 27 files answered ~445,000 characters, because the answer was
|
|
323
|
+
* the detail the app's dialog reads, every patch included. It overflowed the
|
|
324
|
+
* tool-result limit, so the agent could not even read the link.
|
|
325
|
+
*/
|
|
326
|
+
describe('what open_change_request answers', () => {
|
|
327
|
+
const open = async (base: string, include?: string[]) => {
|
|
328
|
+
const res = await post(`${base}/api/agent/tools/open_change_request`, writeTok(), {
|
|
329
|
+
sourceBranch: 'me/draft',
|
|
330
|
+
targetBranch: 'target-company-state',
|
|
331
|
+
title: 'My change',
|
|
332
|
+
...(include ? { include } : {}),
|
|
333
|
+
});
|
|
334
|
+
expect(res.status).toBe(200);
|
|
335
|
+
return (await res.json()) as Record<string, unknown>;
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
it('answers a summary for 27 new files: the link first, 25 paths of 27, approvers, blockers, no patch', async () => {
|
|
339
|
+
const base = await start();
|
|
340
|
+
const body = await open(base);
|
|
341
|
+
|
|
342
|
+
// The link is the FIRST field: the one thing the agent must hand the
|
|
343
|
+
// user, so it survives a truncation of anything after it.
|
|
344
|
+
expect(Object.keys(body)[0]).toBe('url');
|
|
345
|
+
expect(body).toMatchObject({
|
|
346
|
+
url: 'https://bevel.example.com/change-requests/7',
|
|
347
|
+
number: 7,
|
|
348
|
+
title: 'My change',
|
|
349
|
+
state: 'open',
|
|
350
|
+
sourceBranch: 'me/draft',
|
|
351
|
+
targetBranch: 'target-company-state',
|
|
352
|
+
totalFiles: 27,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
const files = body.files as { path: string; change: string }[];
|
|
356
|
+
expect(files).toHaveLength(25);
|
|
357
|
+
expect(new Set(files.map((f) => f.change))).toEqual(new Set(['added']));
|
|
358
|
+
expect(files[0].path).toBe('KnowledgeBase/Engineering/Knowledge/Hexis/Generated-Node-0.md');
|
|
359
|
+
|
|
360
|
+
// Who must approve, per path — what Juan found useful and must stay.
|
|
361
|
+
expect(body.approvals).toHaveLength(25);
|
|
362
|
+
expect((body.approvals as unknown[])[0]).toEqual({
|
|
363
|
+
path: 'KnowledgeBase/Engineering/Knowledge/Hexis/Generated-Node-0.md',
|
|
364
|
+
roles: ['Admin', 'Engineering'],
|
|
365
|
+
users: ['Razvan Radulescu'],
|
|
366
|
+
approved: false,
|
|
367
|
+
inMergeGate: true,
|
|
368
|
+
});
|
|
369
|
+
// What blocks the merge: every reason, for cut paths too.
|
|
370
|
+
expect(body.mergeBlockedReasons).toHaveLength(27);
|
|
371
|
+
|
|
372
|
+
// No patch, and no file content under any other name.
|
|
373
|
+
const json = JSON.stringify(body);
|
|
374
|
+
expect(json).not.toContain('patch');
|
|
375
|
+
expect(json).not.toContain('a line of generated knowledge text');
|
|
376
|
+
expect(files.every((f) => !('patch' in f))).toBe(true);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('keeps the 27-file answer under 10,000 characters', async () => {
|
|
380
|
+
const base = await start();
|
|
381
|
+
const body = await open(base);
|
|
382
|
+
// The detail this was shaped from is the ~445,000-character one.
|
|
383
|
+
expect(JSON.stringify(opened).length).toBeGreaterThan(400_000);
|
|
384
|
+
// 8,918 characters as this fixture stands. The three per-path lists
|
|
385
|
+
// (`files`, `approvals`, `mergeBlockedReasons`) are what fills it, so a
|
|
386
|
+
// field added to any of them eats the margin ~25 times over.
|
|
387
|
+
expect(JSON.stringify(body).length).toBeLessThan(10_000);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('`include: ["all-paths"]` lists all 27 paths, still without patches', async () => {
|
|
391
|
+
const base = await start();
|
|
392
|
+
const body = await open(base, ['all-paths']);
|
|
393
|
+
expect(body.files).toHaveLength(27);
|
|
394
|
+
expect(body.totalFiles).toBe(27);
|
|
395
|
+
expect(body.approvals).toHaveLength(27);
|
|
396
|
+
expect(JSON.stringify(body)).not.toContain('a line of generated knowledge text');
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('`include: ["patches"]` gives each listed file its patch', async () => {
|
|
400
|
+
const base = await start();
|
|
401
|
+
const body = await open(base, ['patches']);
|
|
402
|
+
const files = body.files as { path: string; patch?: string }[];
|
|
403
|
+
expect(files).toHaveLength(25);
|
|
404
|
+
expect(files.every((f) => f.patch?.startsWith('@@ -0,0 +1,400 @@'))).toBe(true);
|
|
405
|
+
// Cut to 25 still: `patches` adds the diffs, it does not lift the cut.
|
|
406
|
+
expect(body.totalFiles).toBe(27);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it('both together: every path, each with its patch', async () => {
|
|
410
|
+
const base = await start();
|
|
411
|
+
const body = await open(base, ['patches', 'all-paths']);
|
|
412
|
+
const files = body.files as { patch?: string }[];
|
|
413
|
+
expect(files).toHaveLength(27);
|
|
414
|
+
expect(files.every((f) => typeof f.patch === 'string')).toBe(true);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it('reads a modification `changed` and a rename `moved`, with its old path', async () => {
|
|
418
|
+
const base = await start();
|
|
419
|
+
opened = detailOf([
|
|
420
|
+
{ path: 'KnowledgeBase/Kept.md', status: 'modified', additions: 2, deletions: 1, patch: '@@ x', isBinary: false, sha: 'b1', rawUrl: 'r1' },
|
|
421
|
+
{ path: 'KnowledgeBase/New-Home.md', previousPath: 'KnowledgeBase/Old-Home.md', status: 'renamed', additions: 0, deletions: 0, isBinary: false, sha: 'b2', rawUrl: 'r2' },
|
|
422
|
+
]);
|
|
423
|
+
const body = await open(base);
|
|
424
|
+
expect(body.files).toEqual([
|
|
425
|
+
{ path: 'KnowledgeBase/Kept.md', change: 'changed' },
|
|
426
|
+
{ path: 'KnowledgeBase/New-Home.md', change: 'moved', previousPath: 'KnowledgeBase/Old-Home.md' },
|
|
427
|
+
]);
|
|
428
|
+
expect(body.totalFiles).toBe(2);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it('a deletion reads `deleted`', async () => {
|
|
432
|
+
const base = await start();
|
|
433
|
+
opened = detailOf([
|
|
434
|
+
{ path: 'KnowledgeBase/Gone.md', status: 'removed', additions: 0, deletions: 9, isBinary: false, sha: 'b3', rawUrl: 'r3' },
|
|
435
|
+
]);
|
|
436
|
+
expect((await open(base)).files).toEqual([{ path: 'KnowledgeBase/Gone.md', change: 'deleted' }]);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('says the approvers are unknown rather than implying nobody must approve', async () => {
|
|
440
|
+
const base = await start();
|
|
441
|
+
opened = detailOf([
|
|
442
|
+
{ path: 'KnowledgeBase/Opaque.md', status: 'added', additions: 1, deletions: 0, isBinary: false, sha: 'b4', rawUrl: 'r4' },
|
|
443
|
+
]);
|
|
444
|
+
// No usable access config on the base: the empty approver set means "not
|
|
445
|
+
// known", and nothing may be granted on the strength of that emptiness.
|
|
446
|
+
opened.approvals = [
|
|
447
|
+
{ path: 'KnowledgeBase/Opaque.md', eligibleApprovers: { roles: [], users: [] }, approvedBy: [], eligibilityResolved: false, isApproved: false, inMergeGate: false },
|
|
448
|
+
];
|
|
449
|
+
expect((await open(base)).approvals).toEqual([
|
|
450
|
+
{ path: 'KnowledgeBase/Opaque.md', roles: [], users: [], approved: false, inMergeGate: false, approversUnknown: true },
|
|
451
|
+
]);
|
|
452
|
+
|
|
453
|
+
// A detail that carries no verdict at all reads the same way: absent is
|
|
454
|
+
// not resolved, so the emptiness still means "not known".
|
|
455
|
+
const legacy = { ...(opened.approvals as Record<string, unknown>[])[0] };
|
|
456
|
+
delete legacy.eligibilityResolved;
|
|
457
|
+
opened.approvals = [legacy];
|
|
458
|
+
expect((await open(base)).approvals).toEqual([
|
|
459
|
+
{ path: 'KnowledgeBase/Opaque.md', roles: [], users: [], approved: false, inMergeGate: false, approversUnknown: true },
|
|
460
|
+
]);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it('surfaces the `change-request-conflicts` error as before', async () => {
|
|
464
|
+
const base = await start();
|
|
465
|
+
openFails = new ChangeRequestConflictsError('me/draft', 'target-company-state', ['KnowledgeBase/Clash.md']);
|
|
466
|
+
const res = await post(`${base}/api/agent/tools/open_change_request`, writeTok(), {
|
|
467
|
+
sourceBranch: 'me/draft',
|
|
468
|
+
targetBranch: 'target-company-state',
|
|
469
|
+
title: 'My change',
|
|
470
|
+
});
|
|
471
|
+
expect(res.status).toBe(409);
|
|
472
|
+
expect(await res.json()).toMatchObject({
|
|
473
|
+
kind: 'change-request-conflicts',
|
|
474
|
+
sourceBranch: 'me/draft',
|
|
475
|
+
targetBranch: 'target-company-state',
|
|
476
|
+
conflictedPaths: ['KnowledgeBase/Clash.md'],
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* The app's dialog reads the same detail from its own route. Shaping the
|
|
482
|
+
* agent's answer must not touch it — so the detail the service handed back
|
|
483
|
+
* still carries all 27 files and all their patches after the tool answered.
|
|
484
|
+
*/
|
|
485
|
+
it('leaves the detail the app\'s dialog reads exactly as the service returned it', async () => {
|
|
486
|
+
const base = await start();
|
|
487
|
+
const before = JSON.stringify(opened);
|
|
488
|
+
await open(base);
|
|
489
|
+
expect(JSON.stringify(opened)).toBe(before);
|
|
490
|
+
expect((opened.files as unknown[])).toHaveLength(27);
|
|
491
|
+
expect((opened.files as { patch?: string }[]).every((f) => typeof f.patch === 'string')).toBe(true);
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it('says in its description what the default answer holds and how to ask for more', async () => {
|
|
495
|
+
await start();
|
|
496
|
+
for (const tools of [await registryRef!.listInternal(), await registryRef!.listExternal()]) {
|
|
497
|
+
const def = tools.find((t) => t.name === 'open_change_request')!;
|
|
498
|
+
expect(def.description).toMatch(/SUMMARY/);
|
|
499
|
+
expect(def.description).toMatch(/cut to 25/);
|
|
500
|
+
expect(def.description).toMatch(/No patches and no file content/);
|
|
501
|
+
expect(def.description).toMatch(/include: \["patches"\]/);
|
|
502
|
+
expect(def.description).toMatch(/include: \["all-paths"\]/);
|
|
503
|
+
// `toolDef` wraps a tool's flat inputs under `body`.
|
|
504
|
+
const body = (def.inputs as { properties: { body: { properties: Record<string, { items?: { enum?: string[] }; description?: string }> } } })
|
|
505
|
+
.properties.body;
|
|
506
|
+
expect(body.properties.include.items?.enum).toEqual(['patches', 'all-paths']);
|
|
507
|
+
expect(body.properties.include.description).toMatch(/every changed path instead of the first 25/);
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
|
|
254
512
|
it('create_branch forks in the BASE branch\'s workspace, never the new draft\'s', async () => {
|
|
255
513
|
const base = await start();
|
|
256
514
|
const res = await post(`${base}/api/agent/tools/create_branch`, writeTok(), {
|