@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,182 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ChangeRequestDetail,
|
|
3
|
+
ChangeRequestState,
|
|
4
|
+
ChangedFile,
|
|
5
|
+
FileApproval,
|
|
6
|
+
PrFileStatus,
|
|
7
|
+
} from '@bevel-software/platform-shared';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* What `open_change_request` answers, and why it is not the detail the app's
|
|
11
|
+
* dialog reads.
|
|
12
|
+
*
|
|
13
|
+
* The dialog renders every file's diff, so its detail carries every patch. An
|
|
14
|
+
* agent that opens a change request needs none of that: it needs the link to
|
|
15
|
+
* hand the user, and enough of the request to confirm it carries the files it
|
|
16
|
+
* meant to propose and to say what the request is now waiting on. Handing it
|
|
17
|
+
* the dialog's detail cost ~445,000 characters for 27 files of 15 KB — past
|
|
18
|
+
* every tool-result limit, so the one field the agent actually needed (the
|
|
19
|
+
* link) could not be read at all.
|
|
20
|
+
*
|
|
21
|
+
* So this is a purpose-made answer: the link first, the request's identity,
|
|
22
|
+
* who must approve each path, what blocks the merge verbatim from the detail,
|
|
23
|
+
* and the changed paths with their kind of change — no patch, no file content.
|
|
24
|
+
* An agent that wants more asks for it by name through `include`.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** What `include` accepts. */
|
|
28
|
+
export type ChangeRequestInclude = 'patches' | 'all-paths';
|
|
29
|
+
|
|
30
|
+
/** The two `include` values, for the tool's input schema and its validation. */
|
|
31
|
+
export const CHANGE_REQUEST_INCLUDES: readonly ChangeRequestInclude[] = ['patches', 'all-paths'];
|
|
32
|
+
|
|
33
|
+
/** How many paths the answer lists before it cuts; `all-paths` lifts the cut. */
|
|
34
|
+
export const CHANGE_REQUEST_PATH_LIMIT = 25;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The kind of change to one path, in the four words the answer speaks. Git's
|
|
38
|
+
* own statuses are finer (`modified` vs `changed`, `renamed` vs `copied`) in
|
|
39
|
+
* ways nothing downstream acts on.
|
|
40
|
+
*/
|
|
41
|
+
export type ChangeKind = 'added' | 'changed' | 'deleted' | 'moved';
|
|
42
|
+
|
|
43
|
+
/** One changed path. `previousPath` is where a `moved` one came from. */
|
|
44
|
+
export interface ChangeRequestSummaryFile {
|
|
45
|
+
path: string;
|
|
46
|
+
change: ChangeKind;
|
|
47
|
+
previousPath?: string;
|
|
48
|
+
/** Unified diff. Only with `include: ["patches"]`, and absent for a binary or oversized file. */
|
|
49
|
+
patch?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Who must approve one path, at the same per-path granularity the detail
|
|
54
|
+
* carries (the access tree resolves a role per file or per folder; this says
|
|
55
|
+
* what it resolved to for this file).
|
|
56
|
+
*
|
|
57
|
+
* The detail's own entry is the dialog's: it also carries each approver's
|
|
58
|
+
* email, the viewer's own eligibility, and every approval's timestamp and
|
|
59
|
+
* staleness. Repeated over 25 files that is ~4,500 characters an agent cannot
|
|
60
|
+
* act on — it may not approve anything — and it is what would push the
|
|
61
|
+
* 27-file answer back over the limit this ticket exists to get under. So the
|
|
62
|
+
* answer keeps what "who must approve" means and nothing else.
|
|
63
|
+
*/
|
|
64
|
+
export interface ChangeRequestApprovers {
|
|
65
|
+
path: string;
|
|
66
|
+
/** Roles that confer approval here. `everyone` means any signed-in approver. */
|
|
67
|
+
roles: string[];
|
|
68
|
+
/** Approvers named directly, by display name. */
|
|
69
|
+
users: string[];
|
|
70
|
+
/** True once an eligible approver has approved at the request's current head. */
|
|
71
|
+
approved: boolean;
|
|
72
|
+
/** False when the merge gate does not bind this path — then nobody has to approve it. */
|
|
73
|
+
inMergeGate: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Present when the access tree could not be resolved for this path — which
|
|
76
|
+
* includes a detail that carries no verdict at all. Then empty
|
|
77
|
+
* `roles`/`users` means "not known", NOT "nobody has to approve".
|
|
78
|
+
*/
|
|
79
|
+
approversUnknown?: true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface ChangeRequestSummary {
|
|
83
|
+
/** First field on purpose: it is the one an agent must hand the user. */
|
|
84
|
+
url: string;
|
|
85
|
+
urlNote?: string;
|
|
86
|
+
number: number;
|
|
87
|
+
title: string;
|
|
88
|
+
state: ChangeRequestState;
|
|
89
|
+
sourceBranch: string;
|
|
90
|
+
targetBranch: string;
|
|
91
|
+
/** Who must approve, one entry per listed path. */
|
|
92
|
+
approvals: ChangeRequestApprovers[];
|
|
93
|
+
/** What blocks the merge — verbatim from the detail. Empty when nothing does. */
|
|
94
|
+
mergeBlockedReasons: string[];
|
|
95
|
+
/** The changed paths, cut to {@link CHANGE_REQUEST_PATH_LIMIT} unless `all-paths` was asked for. */
|
|
96
|
+
files: ChangeRequestSummaryFile[];
|
|
97
|
+
/** How many files the request changes in total, cut or not. */
|
|
98
|
+
totalFiles: number;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* A copy and a rename both carry `previousPath`, and the one thing a reader
|
|
103
|
+
* does with either is look at where the text came from — so both read `moved`.
|
|
104
|
+
* `unchanged` is a file the request lists without changing (git reports it on
|
|
105
|
+
* a mode-only change); `changed` is the honest word for it among these four.
|
|
106
|
+
*/
|
|
107
|
+
function changeKindOf(status: PrFileStatus | undefined): ChangeKind {
|
|
108
|
+
switch (status) {
|
|
109
|
+
case 'added':
|
|
110
|
+
return 'added';
|
|
111
|
+
case 'removed':
|
|
112
|
+
return 'deleted';
|
|
113
|
+
case 'renamed':
|
|
114
|
+
case 'copied':
|
|
115
|
+
return 'moved';
|
|
116
|
+
default:
|
|
117
|
+
return 'changed';
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function approversOf(approval: FileApproval): ChangeRequestApprovers {
|
|
122
|
+
const eligible = approval.eligibleApprovers ?? { roles: [], users: [] };
|
|
123
|
+
return {
|
|
124
|
+
path: approval.path,
|
|
125
|
+
roles: eligible.roles ?? [],
|
|
126
|
+
users: (eligible.users ?? []).map((u) => u.name),
|
|
127
|
+
approved: approval.isApproved,
|
|
128
|
+
inMergeGate: approval.inMergeGate,
|
|
129
|
+
// Anything but an explicit `true` is unresolved — a detail built before
|
|
130
|
+
// the flag existed carries no flag at all, and reading that as "resolved"
|
|
131
|
+
// would turn an empty approver set into "nobody must approve". Same
|
|
132
|
+
// fail-closed reading as `computeViewerCanUpdate`.
|
|
133
|
+
...(approval.eligibilityResolved !== true ? { approversUnknown: true as const } : {}),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function summaryFileOf(file: ChangedFile, withPatch: boolean): ChangeRequestSummaryFile {
|
|
138
|
+
return {
|
|
139
|
+
path: file.path,
|
|
140
|
+
change: changeKindOf(file.status),
|
|
141
|
+
...(file.previousPath ? { previousPath: file.previousPath } : {}),
|
|
142
|
+
...(withPatch && file.patch ? { patch: file.patch } : {}),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Shape the tool's answer out of the detail the service already returns. Pure:
|
|
148
|
+
* the service and the app's routes are untouched, and the detail they serve is
|
|
149
|
+
* exactly what it was.
|
|
150
|
+
*
|
|
151
|
+
* `approvals` is narrowed to the paths the answer lists, because the detail's
|
|
152
|
+
* contract is one entry per listed file. Nothing about the merge gate is lost
|
|
153
|
+
* by that: `mergeBlockedReasons` names every missing approval, cut or not.
|
|
154
|
+
*/
|
|
155
|
+
export function summarizeChangeRequest(
|
|
156
|
+
detail: ChangeRequestDetail,
|
|
157
|
+
include: readonly ChangeRequestInclude[] = [],
|
|
158
|
+
): ChangeRequestSummary {
|
|
159
|
+
const all = detail.files ?? [];
|
|
160
|
+
const withPatch = include.includes('patches');
|
|
161
|
+
const listed = include.includes('all-paths') ? all : all.slice(0, CHANGE_REQUEST_PATH_LIMIT);
|
|
162
|
+
const listedPaths = new Set(listed.map((f) => f.path));
|
|
163
|
+
return {
|
|
164
|
+
url: detail.url,
|
|
165
|
+
...(detail.urlNote ? { urlNote: detail.urlNote } : {}),
|
|
166
|
+
number: detail.number,
|
|
167
|
+
title: detail.title,
|
|
168
|
+
state: detail.state,
|
|
169
|
+
sourceBranch: detail.branch,
|
|
170
|
+
targetBranch: detail.base,
|
|
171
|
+
approvals: (detail.approvals ?? []).filter((a) => listedPaths.has(a.path)).map(approversOf),
|
|
172
|
+
mergeBlockedReasons: detail.mergeBlockedReasons ?? [],
|
|
173
|
+
files: listed.map((f) => summaryFileOf(f, withPatch)),
|
|
174
|
+
totalFiles: all.length,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** The `include` values off a tool call's arguments; anything else is ignored. */
|
|
179
|
+
export function parseInclude(raw: unknown): ChangeRequestInclude[] {
|
|
180
|
+
if (!Array.isArray(raw)) return [];
|
|
181
|
+
return CHANGE_REQUEST_INCLUDES.filter((v) => raw.includes(v));
|
|
182
|
+
}
|
|
@@ -9,6 +9,12 @@ import { assertBranchProvided } from '../../../shared/domain-errors.js';
|
|
|
9
9
|
import { workspaceIdForBranch } from '../../../shared/workspace-id.js';
|
|
10
10
|
import { assertInsideRepo, normalizePathArgs } from '../../kb-fs/repo-path.js';
|
|
11
11
|
import { RETIRED_TOOL_MESSAGES } from '@bevel-software/platform-mcp-core';
|
|
12
|
+
import {
|
|
13
|
+
CHANGE_REQUEST_INCLUDES,
|
|
14
|
+
CHANGE_REQUEST_PATH_LIMIT,
|
|
15
|
+
parseInclude,
|
|
16
|
+
summarizeChangeRequest,
|
|
17
|
+
} from './change-request-summary.js';
|
|
12
18
|
|
|
13
19
|
/** `knowledge-base/KnowledgeBase/x.md` → `KnowledgeBase/x.md`; anything else unchanged. */
|
|
14
20
|
function stripKbDir(path: string, kbDirName: string): string {
|
|
@@ -47,20 +53,31 @@ const commitSchema: JsonSchema = {
|
|
|
47
53
|
required: ['authorName', 'authorEmail', 'sha', 'subject', 'committedAt'],
|
|
48
54
|
};
|
|
49
55
|
|
|
50
|
-
const
|
|
56
|
+
const changeRequestSummaryFileSchema: JsonSchema = {
|
|
51
57
|
type: 'object',
|
|
52
58
|
properties: {
|
|
53
59
|
path: { type: 'string' },
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
change: { type: 'string', description: '`added` | `changed` | `deleted` | `moved`.' },
|
|
61
|
+
previousPath: { type: 'string', description: 'Where a `moved` file came from.' },
|
|
62
|
+
patch: {
|
|
63
|
+
type: 'string',
|
|
64
|
+
description: 'Unified diff. Only with `include: ["patches"]`, and absent for a binary or oversized file.',
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
required: ['path', 'change'],
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const changeRequestApproversSchema: JsonSchema = {
|
|
71
|
+
type: 'object',
|
|
72
|
+
properties: {
|
|
73
|
+
path: { type: 'string' },
|
|
74
|
+
roles: { type: 'array', items: { type: 'string' }, description: 'Roles that confer approval here; `everyone` means any signed-in approver.' },
|
|
75
|
+
users: { type: 'array', items: { type: 'string' }, description: 'Approvers named directly, by display name.' },
|
|
76
|
+
approved: { type: 'boolean', description: 'True once an eligible approver has approved at the current head.' },
|
|
77
|
+
inMergeGate: { type: 'boolean', description: 'False when the merge gate does not bind this path — then nobody has to approve it.' },
|
|
78
|
+
approversUnknown: { type: 'boolean', description: 'Present when the access tree could not be resolved: empty `roles`/`users` means unknown, not nobody.' },
|
|
62
79
|
},
|
|
63
|
-
required: ['path', '
|
|
80
|
+
required: ['path', 'roles', 'users', 'approved', 'inMergeGate'],
|
|
64
81
|
};
|
|
65
82
|
|
|
66
83
|
const commentSchema: JsonSchema = {
|
|
@@ -99,24 +116,43 @@ function linkOf(cr: { number: number; url: string; urlNote?: string }): { number
|
|
|
99
116
|
return { number: cr.number, url: cr.url, ...(cr.urlNote ? { urlNote: cr.urlNote } : {}) };
|
|
100
117
|
}
|
|
101
118
|
|
|
102
|
-
|
|
119
|
+
/**
|
|
120
|
+
* What `open_change_request` answers: a summary, `url` first, with no patch and
|
|
121
|
+
* no file content. The app's dialog reads the full detail from its own route;
|
|
122
|
+
* handing that to an agent cost ~445,000 characters for 27 files of 15 KB and
|
|
123
|
+
* overflowed every tool-result limit. Built by `summarizeChangeRequest`.
|
|
124
|
+
*
|
|
125
|
+
* Property order is the answer's field order, and `url` leads it: the link is
|
|
126
|
+
* the one field the agent must hand the user, so it must survive any truncation.
|
|
127
|
+
*/
|
|
128
|
+
const changeRequestSummarySchema: JsonSchema = {
|
|
103
129
|
type: 'object',
|
|
104
|
-
description: 'Full change-request detail (aliased from the underlying pull request).',
|
|
105
130
|
properties: {
|
|
106
|
-
number: { type: 'integer' },
|
|
107
131
|
url: changeRequestUrlSchema,
|
|
108
132
|
urlNote: changeRequestUrlNoteSchema,
|
|
133
|
+
number: { type: 'integer' },
|
|
109
134
|
title: { type: 'string' },
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
135
|
+
state: { type: 'string', description: '`open` | `merged` | `closed`.' },
|
|
136
|
+
sourceBranch: { type: 'string', description: 'The draft branch carrying the changes.' },
|
|
137
|
+
targetBranch: { type: 'string', description: 'The branch the changes apply to.' },
|
|
138
|
+
approvals: {
|
|
139
|
+
type: 'array',
|
|
140
|
+
items: changeRequestApproversSchema,
|
|
141
|
+
description: 'Who must approve, one entry per listed file. A file nobody can approve (`inMergeGate: false`) blocks nothing.',
|
|
142
|
+
},
|
|
143
|
+
mergeBlockedReasons: {
|
|
144
|
+
type: 'array',
|
|
145
|
+
items: { type: 'string' },
|
|
146
|
+
description: 'What blocks the merge — every missing approval, whether its file is listed or cut. Empty when nothing blocks.',
|
|
147
|
+
},
|
|
148
|
+
files: {
|
|
149
|
+
type: 'array',
|
|
150
|
+
items: changeRequestSummaryFileSchema,
|
|
151
|
+
description: `The changed paths with their kind of change, cut to ${CHANGE_REQUEST_PATH_LIMIT}; \`include: ["all-paths"]\` lists them all.`,
|
|
152
|
+
},
|
|
153
|
+
totalFiles: { type: 'integer', description: 'How many files the request changes in total, cut or not.' },
|
|
117
154
|
},
|
|
118
|
-
required: ['
|
|
119
|
-
additionalProperties: true,
|
|
155
|
+
required: ['url', 'number', 'title', 'state', 'sourceBranch', 'targetBranch', 'approvals', 'mergeBlockedReasons', 'files', 'totalFiles'],
|
|
120
156
|
};
|
|
121
157
|
|
|
122
158
|
/**
|
|
@@ -399,6 +435,12 @@ export function registerWorkflowTools(
|
|
|
399
435
|
'Open a change request from a draft branch into a target branch. Auto-merges the latest target ' +
|
|
400
436
|
'into the source first, pushes, then creates the CR. On conflicts returns a ' +
|
|
401
437
|
'`change-request-conflicts` error with affected paths. The author marker is injected server-side. ' +
|
|
438
|
+
'Answers a SUMMARY, not the request\'s content: `url` first (hand it to the user), then `number`, ' +
|
|
439
|
+
'`title`, `state`, `sourceBranch`, `targetBranch`, `approvals` (who must approve each file), ' +
|
|
440
|
+
'`mergeBlockedReasons` (what blocks the merge), and `files` — the changed paths with their kind of ' +
|
|
441
|
+
`change (\`added\` / \`changed\` / \`deleted\` / \`moved\`), cut to ${CHANGE_REQUEST_PATH_LIMIT} of \`totalFiles\`. ` +
|
|
442
|
+
'No patches and no file content: ask for the diffs with `include: ["patches"]` and for every path ' +
|
|
443
|
+
'with `include: ["all-paths"]`. ' +
|
|
402
444
|
'An agent proposes; a person reviews and merges the request in the app — hand the user its `url`.',
|
|
403
445
|
inputs: {
|
|
404
446
|
type: 'object',
|
|
@@ -407,28 +449,37 @@ export function registerWorkflowTools(
|
|
|
407
449
|
targetBranch: { type: 'string', minLength: 1, description: `The branch to apply to (e.g. '${kb.defaultBranch}').` },
|
|
408
450
|
title: { type: 'string', minLength: 1, maxLength: 256, description: 'Short imperative title (≤256 chars).' },
|
|
409
451
|
description: { type: 'string', description: 'Optional markdown body shown verbatim to reviewers.' },
|
|
452
|
+
include: {
|
|
453
|
+
type: 'array',
|
|
454
|
+
items: { type: 'string', enum: [...CHANGE_REQUEST_INCLUDES] },
|
|
455
|
+
description:
|
|
456
|
+
'Add to the default answer, which carries neither: `patches` gives each listed file its unified diff, ' +
|
|
457
|
+
`\`all-paths\` lists every changed path instead of the first ${CHANGE_REQUEST_PATH_LIMIT}. Both may be given. ` +
|
|
458
|
+
'Patches of a large request run to hundreds of thousands of characters — ask for them only when you will read them.',
|
|
459
|
+
},
|
|
410
460
|
},
|
|
411
461
|
required: ['sourceBranch', 'targetBranch', 'title'],
|
|
412
462
|
additionalProperties: false,
|
|
413
463
|
},
|
|
414
|
-
outputs:
|
|
415
|
-
type: 'object',
|
|
416
|
-
properties: { changeRequest: changeRequestDetailSchema },
|
|
417
|
-
required: ['changeRequest'],
|
|
418
|
-
},
|
|
464
|
+
outputs: changeRequestSummarySchema,
|
|
419
465
|
write: true,
|
|
420
466
|
// The workspace this acts on is the SOURCE draft's — `sourceBranch` already
|
|
421
467
|
// names it, so skip the auto-injected (and here redundant) `branch` input
|
|
422
468
|
// rather than asking the model for both and reading the wrong one.
|
|
423
469
|
skipBranch: true,
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
470
|
+
// The service still returns the full detail — the app's routes serve the
|
|
471
|
+
// same call and must keep getting it. The shaping happens here, in the
|
|
472
|
+
// answer, so nothing below the tool changes.
|
|
473
|
+
handler: async (args, ctx: ToolContext) =>
|
|
474
|
+
summarizeChangeRequest(
|
|
475
|
+
await ctx.workflowService.openChangeRequest(workspaceIdForBranch(args.sourceBranch as string), ctx.user, {
|
|
476
|
+
sourceBranch: args.sourceBranch as string,
|
|
477
|
+
targetBranch: args.targetBranch as string,
|
|
478
|
+
title: args.title as string,
|
|
479
|
+
description: typeof args.description === 'string' ? args.description : undefined,
|
|
480
|
+
}),
|
|
481
|
+
parseInclude(args.include),
|
|
482
|
+
),
|
|
432
483
|
});
|
|
433
484
|
|
|
434
485
|
mount({
|
|
@@ -305,6 +305,37 @@ export class FileLockService {
|
|
|
305
305
|
return rowToFileLock(row);
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Drop every lock held on `branch`, whoever holds it and whichever
|
|
310
|
+
* workspace it sits in, and answer how many rows went.
|
|
311
|
+
*
|
|
312
|
+
* The one caller is a change request being closed because the
|
|
313
|
+
* knowledge-base repository was REPLACED: the branch the lock names belongs
|
|
314
|
+
* to a repository this deployment no longer has, so the holder can neither
|
|
315
|
+
* publish their bytes nor release the lock themselves — and the row would
|
|
316
|
+
* otherwise sit there refusing the path to everyone until its TTL ran out
|
|
317
|
+
* on every heartbeat a still-connected client keeps sending.
|
|
318
|
+
*
|
|
319
|
+
* Expired rows go too: they are already nobody's lock, and leaving them for
|
|
320
|
+
* the lazy sweep would only make this answer harder to read.
|
|
321
|
+
*
|
|
322
|
+
* Deliberately NOT a release in the ordinary sense — nothing is committed
|
|
323
|
+
* and nothing is enqueued. The bytes belonged to a repository that is gone.
|
|
324
|
+
*
|
|
325
|
+
* What an ORDINARY release already enqueued is a different matter, and not
|
|
326
|
+
* this method's to fix: a release queues the bytes and only then drops its
|
|
327
|
+
* lock, so rows can be waiting for a branch whose locks this deletes. The
|
|
328
|
+
* caller takes those out of the worker's reach in the same breath — see
|
|
329
|
+
* `PendingCommitsService.markNeedsAttentionOnBranch`.
|
|
330
|
+
*/
|
|
331
|
+
async releaseAllOnBranch(branch: string): Promise<number> {
|
|
332
|
+
const gone = await this.db
|
|
333
|
+
.delete(fileLocks)
|
|
334
|
+
.where(eq(fileLocks.branch, branch))
|
|
335
|
+
.returning({ path: fileLocks.path });
|
|
336
|
+
return gone.length;
|
|
337
|
+
}
|
|
338
|
+
|
|
308
339
|
/**
|
|
309
340
|
* Is ANY unexpired lock held anywhere in this workspace? Feeds the git
|
|
310
341
|
* status sanity check: a held lock means a mutation is mid-flight — the
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import { testKbContext } from '../../../../__tests__/kb-context.js';
|
|
6
|
+
import { GitService } from '../git.service.js';
|
|
7
|
+
import {
|
|
8
|
+
VersionNotOnBranchError,
|
|
9
|
+
VERSION_NOT_ON_BRANCH_MESSAGE,
|
|
10
|
+
WorkflowValidationError,
|
|
11
|
+
} from '../../../../shared/domain-errors.js';
|
|
12
|
+
import { GitRunError, assertOneSpecPerLine } from '../../../../shared/git.contract.js';
|
|
13
|
+
import { runGit, gitOut, stubWorkflowHooks, stubWorkspaceService } from './git-test-helpers.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A past version has to come back BYTE FOR BYTE, and only for saves on the
|
|
17
|
+
* branch being viewed.
|
|
18
|
+
*
|
|
19
|
+
* `readFileAtRef` decodes git's stdout as UTF-8, which is why a binary could
|
|
20
|
+
* not be served through it at all: every byte sequence that is not valid UTF-8
|
|
21
|
+
* comes back as U+FFFD, so a PNG downloaded from history would not open. The
|
|
22
|
+
* suite pins the round trip on bytes that would not survive that (everything
|
|
23
|
+
* from 0x00 to 0xff, NUL included), the two sides of a save, and the branch
|
|
24
|
+
* rule that decides who may ask at all.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** Every byte value, so any decode-and-re-encode step is visible as a failure. */
|
|
28
|
+
const ALL_BYTES = Buffer.from(Array.from({ length: 256 }, (_, i) => i));
|
|
29
|
+
const PNG_HEADER = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
30
|
+
const V1 = Buffer.concat([PNG_HEADER, ALL_BYTES, Buffer.from('v1')]);
|
|
31
|
+
const V2 = Buffer.concat([PNG_HEADER, ALL_BYTES.subarray(0, 128), Buffer.from('v2\0tail')]);
|
|
32
|
+
|
|
33
|
+
describe('GitService.fileBytesAtCommit', () => {
|
|
34
|
+
let root: string;
|
|
35
|
+
let repo: string;
|
|
36
|
+
let svc: GitService;
|
|
37
|
+
const workspaceId = 'target-company-state';
|
|
38
|
+
/** The save that ADDED logo.png (V1), the save that REPLACED it (V2). */
|
|
39
|
+
let addSha: string;
|
|
40
|
+
let replaceSha: string;
|
|
41
|
+
|
|
42
|
+
beforeEach(async () => {
|
|
43
|
+
root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-bytes-at-ref-'));
|
|
44
|
+
const workspaceDir = path.join(root, workspaceId);
|
|
45
|
+
repo = path.join(workspaceDir, 'knowledge-base');
|
|
46
|
+
await fs.mkdir(repo, { recursive: true });
|
|
47
|
+
await runGit(root, ['init', '-b', 'target-company-state', repo]);
|
|
48
|
+
await runGit(repo, ['config', 'user.email', 'workspace@bevel.test']);
|
|
49
|
+
await runGit(repo, ['config', 'user.name', 'bevel Workspace']);
|
|
50
|
+
|
|
51
|
+
await fs.writeFile(path.join(repo, 'logo.png'), V1);
|
|
52
|
+
await fs.mkdir(path.join(repo, 'docs'));
|
|
53
|
+
await fs.writeFile(path.join(repo, 'docs', 'child.md'), 'child\n');
|
|
54
|
+
await runGit(repo, ['add', '.']);
|
|
55
|
+
await runGit(repo, ['commit', '-m', 'add logo']);
|
|
56
|
+
addSha = await gitOut(repo, ['rev-parse', 'HEAD']);
|
|
57
|
+
|
|
58
|
+
await fs.writeFile(path.join(repo, 'logo.png'), V2);
|
|
59
|
+
await runGit(repo, ['add', '.']);
|
|
60
|
+
await runGit(repo, ['commit', '-m', 'replace logo']);
|
|
61
|
+
replaceSha = await gitOut(repo, ['rev-parse', 'HEAD']);
|
|
62
|
+
|
|
63
|
+
svc = new GitService(
|
|
64
|
+
stubWorkspaceService({ [workspaceId]: workspaceDir }),
|
|
65
|
+
stubWorkflowHooks(),
|
|
66
|
+
testKbContext(),
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
afterEach(async () => {
|
|
71
|
+
await fs.rm(root, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('returns the exact bytes of each save, on both sides of it', async () => {
|
|
75
|
+
const atAdd = await svc.fileBytesAtCommit(workspaceId, 'knowledge-base/logo.png', addSha, 'after');
|
|
76
|
+
expect(atAdd).not.toBeNull();
|
|
77
|
+
expect(atAdd!.bytes.equals(V1)).toBe(true);
|
|
78
|
+
expect(atAdd!.blobId).toMatch(/^[0-9a-f]{40}$/);
|
|
79
|
+
|
|
80
|
+
const atReplace = await svc.fileBytesAtCommit(
|
|
81
|
+
workspaceId,
|
|
82
|
+
'knowledge-base/logo.png',
|
|
83
|
+
replaceSha,
|
|
84
|
+
'after',
|
|
85
|
+
);
|
|
86
|
+
expect(atReplace!.bytes.equals(V2)).toBe(true);
|
|
87
|
+
|
|
88
|
+
// The `before` side of the replacing save IS the original upload — which
|
|
89
|
+
// is what makes "download the version this save replaced" possible.
|
|
90
|
+
const before = await svc.fileBytesAtCommit(
|
|
91
|
+
workspaceId,
|
|
92
|
+
'knowledge-base/logo.png',
|
|
93
|
+
replaceSha,
|
|
94
|
+
'before',
|
|
95
|
+
);
|
|
96
|
+
expect(before!.bytes.equals(V1)).toBe(true);
|
|
97
|
+
// Different content, therefore a different ETag: the blob id IS the hash.
|
|
98
|
+
expect(atReplace!.blobId).not.toBe(before!.blobId);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('accepts the repo-relative spelling of the path too', async () => {
|
|
102
|
+
const at = await svc.fileBytesAtCommit(workspaceId, 'logo.png', addSha, 'after');
|
|
103
|
+
expect(at!.bytes.equals(V1)).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('serves the version just before the save that DELETED the file', async () => {
|
|
107
|
+
await fs.rm(path.join(repo, 'logo.png'));
|
|
108
|
+
await runGit(repo, ['add', '-A']);
|
|
109
|
+
await runGit(repo, ['commit', '-m', 'remove logo']);
|
|
110
|
+
const delSha = await gitOut(repo, ['rev-parse', 'HEAD']);
|
|
111
|
+
|
|
112
|
+
// Nothing at the path after the save…
|
|
113
|
+
expect(
|
|
114
|
+
await svc.fileBytesAtCommit(workspaceId, 'knowledge-base/logo.png', delSha, 'after'),
|
|
115
|
+
).toBeNull();
|
|
116
|
+
// …and the bytes as they were just before it.
|
|
117
|
+
const before = await svc.fileBytesAtCommit(
|
|
118
|
+
workspaceId,
|
|
119
|
+
'knowledge-base/logo.png',
|
|
120
|
+
delSha,
|
|
121
|
+
'before',
|
|
122
|
+
);
|
|
123
|
+
expect(before!.bytes.equals(V2)).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('returns null for a path the save did not have', async () => {
|
|
127
|
+
expect(
|
|
128
|
+
await svc.fileBytesAtCommit(workspaceId, 'knowledge-base/never.png', addSha, 'after'),
|
|
129
|
+
).toBeNull();
|
|
130
|
+
// The root commit has no parent, so it has no `before` side either.
|
|
131
|
+
expect(
|
|
132
|
+
await svc.fileBytesAtCommit(workspaceId, 'knowledge-base/logo.png', addSha, 'before'),
|
|
133
|
+
).toBeNull();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('refuses a directory at the ref — history is served per file', async () => {
|
|
137
|
+
await expect(
|
|
138
|
+
svc.fileBytesAtCommit(workspaceId, 'knowledge-base/docs', addSha, 'after'),
|
|
139
|
+
).rejects.toThrow(/history is served per file/);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('refuses a path carrying a LINE BREAK, which git would read as two object names', async () => {
|
|
143
|
+
// `cat-file --batch` takes one `<ref>:<path>` spec per LINE. Before the
|
|
144
|
+
// path validator refused control characters, `docs/child.md\nzzz` reached
|
|
145
|
+
// it as two specs: git answered about `docs/child.md` and those bytes came
|
|
146
|
+
// back as this path's — under the access gates of a name that exists
|
|
147
|
+
// nowhere, so the file's own rules were never the ones consulted. Verified
|
|
148
|
+
// against real git here, not a stub: the call must fail, and nothing of
|
|
149
|
+
// `child.md` may come back under the longer name.
|
|
150
|
+
for (const injected of [
|
|
151
|
+
'knowledge-base/docs/child.md\nzzz',
|
|
152
|
+
'knowledge-base/docs/child.md\rzzz',
|
|
153
|
+
'knowledge-base/docs/child.md\nknowledge-base/logo.png',
|
|
154
|
+
]) {
|
|
155
|
+
// Both the TYPE and the sentence. Two different validators are called
|
|
156
|
+
// `assertValidRelativePath` — the shared one in
|
|
157
|
+
// `packages/shared/src/workspace/filename.ts`, which throws a plain
|
|
158
|
+
// `Error('Invalid path: …')`, and the kb-fs one in
|
|
159
|
+
// `modules/kb-fs/branch-name.ts`, which throws `WorkflowValidationError`
|
|
160
|
+
// (status 400). `GitService` imports the kb-fs one, and the 400 is what
|
|
161
|
+
// makes the route answer 400 rather than 500, so the type is asserted
|
|
162
|
+
// here deliberately and the message pins which of the two ran.
|
|
163
|
+
await expect(
|
|
164
|
+
svc.fileBytesAtCommit(workspaceId, injected, addSha, 'after'),
|
|
165
|
+
).rejects.toThrow(WorkflowValidationError);
|
|
166
|
+
await expect(
|
|
167
|
+
svc.fileBytesAtCommit(workspaceId, injected, addSha, 'after'),
|
|
168
|
+
).rejects.toThrow(/control character/);
|
|
169
|
+
await expect(
|
|
170
|
+
svc.fileBytesAtCommit(workspaceId, injected, addSha, 'after'),
|
|
171
|
+
).rejects.toMatchObject({ status: 400 });
|
|
172
|
+
}
|
|
173
|
+
// The refusal is the PATH's, decided before the sha is resolved or the
|
|
174
|
+
// workspace's git turn is taken — so a caller cannot reach git with one.
|
|
175
|
+
await expect(
|
|
176
|
+
svc.fileBytesAtCommit(workspaceId, 'knowledge-base/docs/child.md\nzzz', 'HEAD', 'after'),
|
|
177
|
+
).rejects.toThrow(/control character/);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('fences the batch protocol itself against a spec that is not one line', () => {
|
|
181
|
+
// The fence under `blobAtRef` and under the access resolver's batch read,
|
|
182
|
+
// for a spec assembled from somewhere the path validator does not cover.
|
|
183
|
+
expect(() => assertOneSpecPerLine([`${addSha}:logo.png`])).not.toThrow();
|
|
184
|
+
expect(() => assertOneSpecPerLine([`${addSha}:docs/child.md\nzzz`])).toThrow(/line break/);
|
|
185
|
+
expect(() => assertOneSpecPerLine(['a:b', 'c:d\re'])).toThrow(/line break/);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('refuses a sha that is not a sha', async () => {
|
|
189
|
+
await expect(
|
|
190
|
+
svc.fileBytesAtCommit(workspaceId, 'knowledge-base/logo.png', 'HEAD', 'after'),
|
|
191
|
+
).rejects.toThrow(WorkflowValidationError);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('refuses a save that is not in this branch\'s history, and a made-up one, alike', async () => {
|
|
195
|
+
// A save that exists — on another branch this workspace is not viewing.
|
|
196
|
+
await runGit(repo, ['checkout', '-b', 'someone-else']);
|
|
197
|
+
await fs.writeFile(path.join(repo, 'logo.png'), Buffer.from('elsewhere'));
|
|
198
|
+
await runGit(repo, ['add', '-A']);
|
|
199
|
+
await runGit(repo, ['commit', '-m', 'elsewhere']);
|
|
200
|
+
const otherSha = await gitOut(repo, ['rev-parse', 'HEAD']);
|
|
201
|
+
await runGit(repo, ['checkout', 'target-company-state']);
|
|
202
|
+
|
|
203
|
+
for (const sha of [otherSha, 'a'.repeat(40)]) {
|
|
204
|
+
const read = svc.fileBytesAtCommit(workspaceId, 'knowledge-base/logo.png', sha, 'after');
|
|
205
|
+
await expect(read).rejects.toThrow(VersionNotOnBranchError);
|
|
206
|
+
await expect(read).rejects.toThrow(VERSION_NOT_ON_BRANCH_MESSAGE);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// …and every save the panel LISTS is accepted, which is the other half of
|
|
210
|
+
// the rule: `logForFile` walks HEAD, so anything it returns is an ancestor.
|
|
211
|
+
// Asserted through the read itself rather than a boolean helper, so what is
|
|
212
|
+
// pinned is the thing the route calls.
|
|
213
|
+
for (const listed of await svc.logForFile(workspaceId, 'knowledge-base/logo.png')) {
|
|
214
|
+
await expect(
|
|
215
|
+
svc.fileBytesAtCommit(workspaceId, 'knowledge-base/logo.png', listed.sha, 'after'),
|
|
216
|
+
).resolves.not.toBeNull();
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('propagates a repository failure rather than calling it a version off the branch', async () => {
|
|
221
|
+
// An UNBORN HEAD: the sha resolves perfectly well and git's complaint is
|
|
222
|
+
// about the other argument ('ambiguous argument HEAD'). Folded into
|
|
223
|
+
// `VersionNotOnBranchError` it would tell a reader the save they can see
|
|
224
|
+
// listed is not in their history — a broken workspace dressed up as an
|
|
225
|
+
// answer about access, and one 'Try again' can never get past.
|
|
226
|
+
await runGit(repo, ['checkout', '--orphan', 'nothing-committed-yet']);
|
|
227
|
+
const err: unknown = await svc
|
|
228
|
+
.fileBytesAtCommit(workspaceId, 'knowledge-base/logo.png', addSha, 'after')
|
|
229
|
+
.catch((e: unknown) => e);
|
|
230
|
+
// `GitRunError` specifically, not just `Error`: the route reads the error's
|
|
231
|
+
// TYPE to decide its status, so a future re-wrap into
|
|
232
|
+
// `WorkflowValidationError` would turn this into a 400 about the caller's
|
|
233
|
+
// request rather than the logged 500 a broken repository earns, and a bare
|
|
234
|
+
// `toBeInstanceOf(Error)` would not notice.
|
|
235
|
+
expect(err).toBeInstanceOf(GitRunError);
|
|
236
|
+
expect(err).not.toBeInstanceOf(VersionNotOnBranchError);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('applies the same branch rule to the existing text-history reads', async () => {
|
|
240
|
+
await runGit(repo, ['checkout', '-b', 'someone-else']);
|
|
241
|
+
await fs.writeFile(path.join(repo, 'logo.png'), Buffer.from('elsewhere'));
|
|
242
|
+
await runGit(repo, ['add', '-A']);
|
|
243
|
+
await runGit(repo, ['commit', '-m', 'elsewhere']);
|
|
244
|
+
const otherSha = await gitOut(repo, ['rev-parse', 'HEAD']);
|
|
245
|
+
await runGit(repo, ['checkout', 'target-company-state']);
|
|
246
|
+
|
|
247
|
+
// Otherwise the bytes route's rule could simply be read around: the patch
|
|
248
|
+
// and the full before/after text are past versions too.
|
|
249
|
+
await expect(
|
|
250
|
+
svc.diffFileAtCommit(workspaceId, 'knowledge-base/logo.png', otherSha),
|
|
251
|
+
).rejects.toThrow(VERSION_NOT_ON_BRANCH_MESSAGE);
|
|
252
|
+
await expect(
|
|
253
|
+
svc.fileContentsAtCommit(workspaceId, 'knowledge-base/logo.png', otherSha),
|
|
254
|
+
).rejects.toThrow(VERSION_NOT_ON_BRANCH_MESSAGE);
|
|
255
|
+
// The on-branch saves still work.
|
|
256
|
+
expect(await svc.diffFileAtCommit(workspaceId, 'knowledge-base/logo.png', replaceSha)).toContain(
|
|
257
|
+
'logo.png',
|
|
258
|
+
);
|
|
259
|
+
});
|
|
260
|
+
});
|