@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
|
@@ -343,6 +343,7 @@ export class ReviewWorkflowService implements IReviewWorkflowService {
|
|
|
343
343
|
.values({
|
|
344
344
|
prNumber,
|
|
345
345
|
authorEmail: canonicalEmail(user.email),
|
|
346
|
+
authorEmailBidx: user.email,
|
|
346
347
|
authorName: user.name,
|
|
347
348
|
path: input.path ?? null,
|
|
348
349
|
line: input.line ?? null,
|
|
@@ -600,6 +601,7 @@ export class ReviewWorkflowService implements IReviewWorkflowService {
|
|
|
600
601
|
prNumber,
|
|
601
602
|
path,
|
|
602
603
|
approverEmail: callerEmail,
|
|
604
|
+
approverEmailBidx: callerEmail,
|
|
603
605
|
approverName: user.name,
|
|
604
606
|
headSha,
|
|
605
607
|
})
|
|
@@ -625,10 +627,13 @@ export class ReviewWorkflowService implements IReviewWorkflowService {
|
|
|
625
627
|
erased: { email: string; name: string },
|
|
626
628
|
): Promise<void> {
|
|
627
629
|
await takeEveryApprovalLock(tx);
|
|
630
|
+
// Matched via the blind index — the email column is randomized
|
|
631
|
+
// ciphertext — and the index is rewritten to the placeholder's too, so no
|
|
632
|
+
// value keyed to the erased address survives.
|
|
628
633
|
await tx
|
|
629
634
|
.update(prFileApprovals)
|
|
630
|
-
.set({ approverEmail: erased.email, approverName: erased.name })
|
|
631
|
-
.where(eq(prFileApprovals.
|
|
635
|
+
.set({ approverEmail: erased.email, approverEmailBidx: erased.email, approverName: erased.name })
|
|
636
|
+
.where(eq(prFileApprovals.approverEmailBidx, email));
|
|
632
637
|
}
|
|
633
638
|
|
|
634
639
|
/**
|
|
@@ -714,6 +719,7 @@ export class ReviewWorkflowService implements IReviewWorkflowService {
|
|
|
714
719
|
.values({
|
|
715
720
|
prNumber,
|
|
716
721
|
triggeredByEmail,
|
|
722
|
+
triggeredByEmailBidx: triggeredByEmail,
|
|
717
723
|
triggeredByName: user.name,
|
|
718
724
|
headShaAtMerge: headSha,
|
|
719
725
|
mergeMethod: MERGE_METHOD,
|
|
@@ -930,6 +936,9 @@ export class ReviewWorkflowService implements IReviewWorkflowService {
|
|
|
930
936
|
prNumber,
|
|
931
937
|
path: r.path,
|
|
932
938
|
approverEmail: r.approverEmail,
|
|
939
|
+
// The address, not the stored index read back beside it: an index
|
|
940
|
+
// column is written with what it is the index of.
|
|
941
|
+
approverEmailBidx: r.approverEmail,
|
|
933
942
|
approverName: r.approverName,
|
|
934
943
|
headSha: toHeadSha,
|
|
935
944
|
approvedAt: r.approvedAt,
|
|
@@ -1013,7 +1022,7 @@ export class ReviewWorkflowService implements IReviewWorkflowService {
|
|
|
1013
1022
|
and(
|
|
1014
1023
|
eq(prFileApprovals.prNumber, prNumber),
|
|
1015
1024
|
eq(prFileApprovals.path, path),
|
|
1016
|
-
eq(prFileApprovals.
|
|
1025
|
+
eq(prFileApprovals.approverEmailBidx, callerEmail),
|
|
1017
1026
|
),
|
|
1018
1027
|
);
|
|
1019
1028
|
});
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import type { ValidationReport } from '@bevel-software/platform-shared';
|
|
1
|
+
import type { AuthUser, ValidationReport } from '@bevel-software/platform-shared';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Workflow lifecycle hooks — the generic seam that replaced the
|
|
5
|
-
* constructor-injected KB validator
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* registers none, so a core-only deployment runs every hook point as a no-op.
|
|
5
|
+
* constructor-injected KB validator. Core code invokes the hooks at fixed
|
|
6
|
+
* lifecycle points; the modules that OWN the behavior register handlers in the
|
|
7
|
+
* composition root, right after `createCoreServices` returns. Core registers
|
|
8
|
+
* none, so a core-only deployment runs every hook point as a no-op.
|
|
10
9
|
*
|
|
11
|
-
*
|
|
10
|
+
* Three hook kinds, with deliberately different failure semantics:
|
|
12
11
|
*
|
|
13
12
|
* - `commitValidation` — ADVISORY. Runs at commit time in `GitService`
|
|
14
13
|
* (`commit` / `commitFile`), exactly where the injected `IKbValidator`
|
|
@@ -18,12 +17,18 @@ import type { ValidationReport } from '@bevel-software/platform-shared';
|
|
|
18
17
|
* `runValidation(workspaceId)` shape so `KbValidatorService` registers
|
|
19
18
|
* with a one-line adapter.
|
|
20
19
|
*
|
|
21
|
-
* - `
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
20
|
+
* - `agentRead` — BLOCKING. Runs before a gated agent READ of a
|
|
21
|
+
* knowledge-base path (reads, `delete_file`, `delete_folder`, and each
|
|
22
|
+
* file a `grep` walk opens). A hook that throws REFUSES the read.
|
|
23
|
+
*
|
|
24
|
+
* - `preWrite` — BLOCKING. Runs before a gated agent WRITE (the file-write
|
|
25
|
+
* tools, once per path) or a write-capable shell command, which carries no
|
|
26
|
+
* path. A hook that throws REFUSES the operation, and the caller sees the
|
|
27
|
+
* hook's own message and status.
|
|
28
|
+
*
|
|
29
|
+
* The two blocking hooks are the seam a deployment builds a per-conversation
|
|
30
|
+
* rule on: they carry the session id the call named, and core itself neither
|
|
31
|
+
* decides nor records anything (see `workspace/agent-access.gate.ts`).
|
|
27
32
|
*/
|
|
28
33
|
|
|
29
34
|
/** What `GitService` knows at the advisory commit-validation point. */
|
|
@@ -47,19 +52,35 @@ export type CommitValidationHook = (
|
|
|
47
52
|
) => Promise<ValidationReport | void>;
|
|
48
53
|
|
|
49
54
|
/**
|
|
50
|
-
* What
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
55
|
+
* What an agent operation looks like to the blocking hooks. The core gate has
|
|
56
|
+
* already resolved the only two skips it owns (the caller is not an agent, or
|
|
57
|
+
* is the recovery bot), so every hook call describes a real agent operation.
|
|
58
|
+
*
|
|
59
|
+
* `sessionId` is present only when the call carried one — core does NOT fail
|
|
60
|
+
* closed on its absence, because whether a path needs a session is the
|
|
61
|
+
* registering deployment's rule, not core's.
|
|
54
62
|
*/
|
|
55
|
-
export
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
|
|
63
|
+
export interface AgentOperationContext {
|
|
64
|
+
/** The conversation the call belongs to, when it named one. */
|
|
65
|
+
sessionId?: string;
|
|
66
|
+
/**
|
|
67
|
+
* The workspace-relative path the operation targets. Absent for a
|
|
68
|
+
* write-capable operation with no resolvable target (`execute_command`).
|
|
69
|
+
*/
|
|
70
|
+
wsPath?: string;
|
|
71
|
+
/** The branch (draft) whose workspace the operation acts on. */
|
|
72
|
+
branch: string;
|
|
73
|
+
/** The calling user. */
|
|
74
|
+
user: AuthUser;
|
|
75
|
+
/** Whether the caller is the in-app agent (`internal`) or an external one. */
|
|
76
|
+
source: 'internal' | 'external';
|
|
77
|
+
}
|
|
60
78
|
|
|
61
|
-
/** Blocking pre-
|
|
62
|
-
export type
|
|
79
|
+
/** Blocking pre-read hook: throw to refuse the read; return to allow. */
|
|
80
|
+
export type AgentReadHook = (ctx: AgentOperationContext) => Promise<void>;
|
|
81
|
+
|
|
82
|
+
/** Blocking pre-write hook: throw to refuse the write; return to allow. */
|
|
83
|
+
export type PreWriteHook = (ctx: AgentOperationContext) => Promise<void>;
|
|
63
84
|
|
|
64
85
|
/**
|
|
65
86
|
* The registry instance. ONE per composition (created in
|
|
@@ -68,6 +89,7 @@ export type PreWriteHook = (ctx: PreWriteContext) => Promise<void>;
|
|
|
68
89
|
*/
|
|
69
90
|
export class WorkflowHooks {
|
|
70
91
|
private readonly commitValidation: CommitValidationHook[] = [];
|
|
92
|
+
private readonly agentRead: AgentReadHook[] = [];
|
|
71
93
|
private readonly preWrite: PreWriteHook[] = [];
|
|
72
94
|
|
|
73
95
|
/** Register an advisory commit-time validation hook. */
|
|
@@ -75,6 +97,11 @@ export class WorkflowHooks {
|
|
|
75
97
|
this.commitValidation.push(hook);
|
|
76
98
|
}
|
|
77
99
|
|
|
100
|
+
/** Register a blocking pre-read hook. */
|
|
101
|
+
onAgentRead(hook: AgentReadHook): void {
|
|
102
|
+
this.agentRead.push(hook);
|
|
103
|
+
}
|
|
104
|
+
|
|
78
105
|
/** Register a blocking pre-write hook. */
|
|
79
106
|
onPreWrite(hook: PreWriteHook): void {
|
|
80
107
|
this.preWrite.push(hook);
|
|
@@ -88,12 +115,23 @@ export class WorkflowHooks {
|
|
|
88
115
|
return this.commitValidation;
|
|
89
116
|
}
|
|
90
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Run every blocking pre-read hook in registration order. The first throw
|
|
120
|
+
* propagates and refuses the read; with no hooks registered (core-only)
|
|
121
|
+
* this resolves immediately.
|
|
122
|
+
*/
|
|
123
|
+
async runAgentRead(ctx: AgentOperationContext): Promise<void> {
|
|
124
|
+
for (const hook of this.agentRead) {
|
|
125
|
+
await hook(ctx);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
91
129
|
/**
|
|
92
130
|
* Run every blocking pre-write hook in registration order. The first throw
|
|
93
|
-
* propagates and
|
|
131
|
+
* propagates and refuses the write; with no hooks registered (core-only)
|
|
94
132
|
* this resolves immediately.
|
|
95
133
|
*/
|
|
96
|
-
async runPreWrite(ctx:
|
|
134
|
+
async runPreWrite(ctx: AgentOperationContext): Promise<void> {
|
|
97
135
|
for (const hook of this.preWrite) {
|
|
98
136
|
await hook(ctx);
|
|
99
137
|
}
|
|
@@ -63,7 +63,7 @@ import {
|
|
|
63
63
|
isPlatformRestoreShape,
|
|
64
64
|
} from '@bevel-software/platform-shared';
|
|
65
65
|
import type { KbContext } from '../../shared/kb-context.js';
|
|
66
|
-
import { and, desc, eq, inArray, isNotNull, isNull, lt, ne, or } from 'drizzle-orm';
|
|
66
|
+
import { and, desc, eq, inArray, isNotNull, isNull, lt, ne, or, sql } from 'drizzle-orm';
|
|
67
67
|
import type { Database } from '../database/connection.js';
|
|
68
68
|
import { changeRequests } from '../database/schema.js';
|
|
69
69
|
import type { GitService } from './git/git.service.js';
|
|
@@ -324,9 +324,9 @@ export class WorkflowService implements IWorkflowService {
|
|
|
324
324
|
* the composition root registers module-owned behavior against it
|
|
325
325
|
* (`core.workflowService.hooks.onCommitValidation(…)` /
|
|
326
326
|
* `.onPreWrite(…)`) after `createCoreServices` returns. The composition
|
|
327
|
-
* root passes the SAME instance `GitService` and the
|
|
328
|
-
*
|
|
329
|
-
*
|
|
327
|
+
* root passes the SAME instance `GitService` and the agent-access gate
|
|
328
|
+
* consult; the default keeps test constructions (which don't exercise
|
|
329
|
+
* hooks) unchanged.
|
|
330
330
|
*/
|
|
331
331
|
public readonly hooks: WorkflowHooks = new WorkflowHooks(),
|
|
332
332
|
) {
|
|
@@ -910,6 +910,22 @@ export class WorkflowService implements IWorkflowService {
|
|
|
910
910
|
return this.git.fileContentsAtCommit(workspaceId, path, sha);
|
|
911
911
|
}
|
|
912
912
|
|
|
913
|
+
/**
|
|
914
|
+
* The BYTES of one file at a change — what the file page's byte-reading
|
|
915
|
+
* viewers (pdf, image, docx, pptx, xlsx, email) need to show a past save,
|
|
916
|
+
* and what "Download this version" hands over. `fileAtChange` above cannot
|
|
917
|
+
* serve them: it decodes to UTF-8, which ruins every format that is not
|
|
918
|
+
* text.
|
|
919
|
+
*/
|
|
920
|
+
fileBytesAtChange(
|
|
921
|
+
workspaceId: string,
|
|
922
|
+
path: string,
|
|
923
|
+
sha: string,
|
|
924
|
+
side: 'after' | 'before',
|
|
925
|
+
): Promise<{ bytes: Buffer; blobId: string } | null> {
|
|
926
|
+
return this.git.fileBytesAtCommit(workspaceId, path, sha, side);
|
|
927
|
+
}
|
|
928
|
+
|
|
913
929
|
/**
|
|
914
930
|
* Resolve write permission for `targetPath` against the access tree at
|
|
915
931
|
* `HEAD` of `branch`. Throws `AccessDeniedError` on denial with the
|
|
@@ -1754,7 +1770,7 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1754
1770
|
.from(changeRequests)
|
|
1755
1771
|
.where(
|
|
1756
1772
|
and(
|
|
1757
|
-
eq(changeRequests.
|
|
1773
|
+
eq(changeRequests.authorEmailBidx, email),
|
|
1758
1774
|
eq(changeRequests.sourceBranch, sourceBranch),
|
|
1759
1775
|
ne(changeRequests.state, 'open'),
|
|
1760
1776
|
),
|
|
@@ -1894,6 +1910,7 @@ export class WorkflowService implements IWorkflowService {
|
|
|
1894
1910
|
title: input.title.trim(),
|
|
1895
1911
|
body,
|
|
1896
1912
|
authorEmail: canonicalEmail(user.email),
|
|
1913
|
+
authorEmailBidx: user.email,
|
|
1897
1914
|
authorName: user.name,
|
|
1898
1915
|
})
|
|
1899
1916
|
.returning({ number: changeRequests.number });
|
|
@@ -3024,6 +3041,107 @@ export class WorkflowService implements IWorkflowService {
|
|
|
3024
3041
|
return closed;
|
|
3025
3042
|
}
|
|
3026
3043
|
|
|
3044
|
+
/**
|
|
3045
|
+
* How many change requests are open right now.
|
|
3046
|
+
*
|
|
3047
|
+
* Read by the setup screen's repository-change confirmation, which has to
|
|
3048
|
+
* say how many requests the admin is deciding about BEFORE the address is
|
|
3049
|
+
* saved. A plain count — nothing here reaches the repository, so it answers
|
|
3050
|
+
* just as well for a repository that is already gone.
|
|
3051
|
+
*/
|
|
3052
|
+
async countOpenChangeRequests(): Promise<number> {
|
|
3053
|
+
const [row] = await this.db
|
|
3054
|
+
.select({ open: sql<number>`count(*)::int` })
|
|
3055
|
+
.from(changeRequests)
|
|
3056
|
+
.where(eq(changeRequests.state, 'open'));
|
|
3057
|
+
return row?.open ?? 0;
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
/**
|
|
3061
|
+
* Close every open change request because the knowledge-base REPOSITORY was
|
|
3062
|
+
* replaced, and answer how many were closed.
|
|
3063
|
+
*
|
|
3064
|
+
* The admin's own choice on the setup screen: a repository that merely
|
|
3065
|
+
* MOVED keeps its requests (the branches came along), while a repository
|
|
3066
|
+
* that was REPLACED has none of those branches, so every request points at
|
|
3067
|
+
* something this deployment can no longer fetch. Nothing is deleted — the
|
|
3068
|
+
* rows stay, closed, carrying `closed_reason = 'repository-replaced'` so
|
|
3069
|
+
* the reason survives the person who chose it.
|
|
3070
|
+
*
|
|
3071
|
+
* Locks held on those branches go with them. Their holder cannot publish
|
|
3072
|
+
* the bytes (the branch is not in the new repository) and cannot release
|
|
3073
|
+
* the lock by finishing (the request is closed), so the row would sit there
|
|
3074
|
+
* refusing the path until its TTL ran out — and a still-connected client's
|
|
3075
|
+
* heartbeat keeps pushing that out.
|
|
3076
|
+
*
|
|
3077
|
+
* The close is ONE statement per pass — `update … where state = 'open'
|
|
3078
|
+
* returning` — not a count followed by a row-at-a-time loop. Read first and
|
|
3079
|
+
* then written, every request opened while the loop ran would be missed and
|
|
3080
|
+
* left open against a branch the new repository does not have. One
|
|
3081
|
+
* set-shaped statement cannot miss a row that exists when it runs, and the
|
|
3082
|
+
* guard is the statement's own `where`, so a merge or a withdrawal racing it
|
|
3083
|
+
* wins and its request is simply not in the returned set.
|
|
3084
|
+
*
|
|
3085
|
+
* Repeated while a pass still finds something, because a request can be
|
|
3086
|
+
* opened BETWEEN passes: the caller has not saved the new address yet, so
|
|
3087
|
+
* the deployment is still live and `POST /change-requests` is still being
|
|
3088
|
+
* served. Bounded, so a client opening requests in a loop cannot hold the
|
|
3089
|
+
* save open forever — what survives the last pass is a request opened after
|
|
3090
|
+
* the repository was replaced, which the deleted-branch sweep closes on its
|
|
3091
|
+
* own terms.
|
|
3092
|
+
*/
|
|
3093
|
+
async closeOpenChangeRequestsAsRepositoryReplaced(): Promise<number> {
|
|
3094
|
+
let closed = 0;
|
|
3095
|
+
for (let pass = 0; pass < 5; pass++) {
|
|
3096
|
+
const now = new Date();
|
|
3097
|
+
const justClosed = await this.db
|
|
3098
|
+
.update(changeRequests)
|
|
3099
|
+
.set({ state: 'closed', closedAt: now, updatedAt: now, closedReason: 'repository-replaced' })
|
|
3100
|
+
.where(eq(changeRequests.state, 'open'))
|
|
3101
|
+
.returning({ number: changeRequests.number, sourceBranch: changeRequests.sourceBranch });
|
|
3102
|
+
if (justClosed.length === 0) break;
|
|
3103
|
+
for (const cr of justClosed) {
|
|
3104
|
+
// Best effort, per branch: a lock table that will not answer must not
|
|
3105
|
+
// leave the requests half closed — the close is the decision the admin
|
|
3106
|
+
// made, and the locks expire on their own within the minute.
|
|
3107
|
+
try {
|
|
3108
|
+
const released = await this.fileLocks.releaseAllOnBranch(cr.sourceBranch);
|
|
3109
|
+
if (released > 0) {
|
|
3110
|
+
crLog.info(`released ${released} file lock(s) held on "${cr.sourceBranch}"`);
|
|
3111
|
+
}
|
|
3112
|
+
} catch (err) {
|
|
3113
|
+
crLog.warn(`could not release the file locks on "${cr.sourceBranch}":`, { err });
|
|
3114
|
+
}
|
|
3115
|
+
// The bytes a release already enqueued belong to the repository that
|
|
3116
|
+
// is gone — see `markNeedsAttentionOnBranch`. Taken out of the
|
|
3117
|
+
// worker's reach in the same breath as the locks, because dropping a
|
|
3118
|
+
// lock is precisely what leaves a row behind.
|
|
3119
|
+
try {
|
|
3120
|
+
const quarantined = await this.pendingCommits.markNeedsAttentionOnBranch(
|
|
3121
|
+
cr.sourceBranch,
|
|
3122
|
+
'The knowledge-base repository was replaced while this commit was still queued, so it was never ' +
|
|
3123
|
+
'written. The bytes are kept here: the branch it was meant for belongs to the previous repository.',
|
|
3124
|
+
);
|
|
3125
|
+
if (quarantined > 0) {
|
|
3126
|
+
crLog.warn(
|
|
3127
|
+
`${quarantined} queued commit(s) on "${cr.sourceBranch}" need attention: the repository was replaced ` +
|
|
3128
|
+
'before they landed, so they were not written to the new one.',
|
|
3129
|
+
);
|
|
3130
|
+
}
|
|
3131
|
+
} catch (err) {
|
|
3132
|
+
crLog.warn(`could not set aside the queued commits on "${cr.sourceBranch}":`, { err });
|
|
3133
|
+
}
|
|
3134
|
+
this.prs.invalidateDetailCache(cr.number);
|
|
3135
|
+
this.events?.emit({ kind: 'change-request-rejected', number: cr.number });
|
|
3136
|
+
closed++;
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
if (closed > 0) {
|
|
3140
|
+
crLog.info(`closed ${closed} change request(s): the knowledge-base repository was replaced`);
|
|
3141
|
+
}
|
|
3142
|
+
return closed;
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3027
3145
|
/**
|
|
3028
3146
|
* The sweep above, on demand. Boot runs it once (`create-core-server`),
|
|
3029
3147
|
* which clears requests stranded BEFORE the process started; a branch that
|
|
@@ -3460,6 +3578,7 @@ export class WorkflowService implements IWorkflowService {
|
|
|
3460
3578
|
applyFailureKind: failure.kind,
|
|
3461
3579
|
applyFailedAt: at,
|
|
3462
3580
|
applyFailedByName: user.name,
|
|
3581
|
+
applyFailedByEmailBidx: user.email,
|
|
3463
3582
|
})
|
|
3464
3583
|
// The write itself refuses to go backwards: the attempt map above is an
|
|
3465
3584
|
// early exit within this process, but an older UPDATE still in flight (or
|
|
@@ -3520,6 +3639,7 @@ export class WorkflowService implements IWorkflowService {
|
|
|
3520
3639
|
applyFailureConflicts: null,
|
|
3521
3640
|
applyFailedAt: null,
|
|
3522
3641
|
applyFailedByName: null,
|
|
3642
|
+
applyFailedByEmailBidx: null,
|
|
3523
3643
|
applyFailureKind: null,
|
|
3524
3644
|
})
|
|
3525
3645
|
.where(
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { describe, expect, it, beforeEach } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
notifyAgentRead,
|
|
4
|
+
assertAgentWriteAllowed,
|
|
5
|
+
SESSION_ID_DESCRIPTION,
|
|
6
|
+
SESSION_ID_INPUT,
|
|
7
|
+
ToolDescriptionNotes,
|
|
8
|
+
type AgentAccessGate,
|
|
9
|
+
} from '../agent-access.gate.js';
|
|
10
|
+
import { WorkflowHooks, type AgentOperationContext } from '../../workflow/workflow-hooks.js';
|
|
11
|
+
import { ToolError, type ToolContext } from '../../tool-helpers/tool.contract.js';
|
|
12
|
+
|
|
13
|
+
const RECOVERY_EMAIL = 'recovery-bot@bevel.local';
|
|
14
|
+
const P_FILE = 'knowledge-base/KnowledgeBase/Product/Knowledge/Roadmap.md';
|
|
15
|
+
|
|
16
|
+
function ctx(over: Partial<ToolContext> = {}): ToolContext {
|
|
17
|
+
return {
|
|
18
|
+
user: { id: 'u1', email: 'a@b.c', name: 'A' },
|
|
19
|
+
scope: 'write',
|
|
20
|
+
source: 'external',
|
|
21
|
+
sessionId: 's1',
|
|
22
|
+
abortSignal: new AbortController().signal,
|
|
23
|
+
workspaceService: {} as never,
|
|
24
|
+
workflowService: {} as never,
|
|
25
|
+
events: {} as never,
|
|
26
|
+
getFilesystem: async () => ({}) as never,
|
|
27
|
+
...over,
|
|
28
|
+
} as ToolContext;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A gate with a fresh hook registry, plus the two lists of what the hooks saw. */
|
|
32
|
+
function makeGate(): { gate: AgentAccessGate; reads: AgentOperationContext[]; writes: AgentOperationContext[] } {
|
|
33
|
+
const hooks = new WorkflowHooks();
|
|
34
|
+
const reads: AgentOperationContext[] = [];
|
|
35
|
+
const writes: AgentOperationContext[] = [];
|
|
36
|
+
hooks.onAgentRead(async (op) => {
|
|
37
|
+
reads.push(op);
|
|
38
|
+
});
|
|
39
|
+
hooks.onPreWrite(async (op) => {
|
|
40
|
+
writes.push(op);
|
|
41
|
+
});
|
|
42
|
+
return { gate: { recoveryBotEmail: RECOVERY_EMAIL, hooks, notes: new ToolDescriptionNotes() }, reads, writes };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe('notifyAgentRead', () => {
|
|
46
|
+
it('calls the read hook once with the session, path, branch, user and source', async () => {
|
|
47
|
+
const { gate, reads, writes } = makeGate();
|
|
48
|
+
await notifyAgentRead(gate, ctx(), 'draft-1', P_FILE);
|
|
49
|
+
expect(reads).toEqual([
|
|
50
|
+
{
|
|
51
|
+
sessionId: 's1',
|
|
52
|
+
wsPath: P_FILE,
|
|
53
|
+
branch: 'draft-1',
|
|
54
|
+
user: { id: 'u1', email: 'a@b.c', name: 'A' },
|
|
55
|
+
source: 'external',
|
|
56
|
+
},
|
|
57
|
+
]);
|
|
58
|
+
expect(writes).toEqual([]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('carries the in-app agent as source `internal`', async () => {
|
|
62
|
+
const { gate, reads } = makeGate();
|
|
63
|
+
await notifyAgentRead(gate, ctx({ source: 'internal' }), 'main', P_FILE);
|
|
64
|
+
expect(reads[0].source).toBe('internal');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('calls the hook with no session id when the call carried none — the hook decides', async () => {
|
|
68
|
+
const { gate, reads } = makeGate();
|
|
69
|
+
await notifyAgentRead(gate, ctx({ sessionId: undefined }), 'main', P_FILE);
|
|
70
|
+
expect(reads).toHaveLength(1);
|
|
71
|
+
expect(reads[0].sessionId).toBeUndefined();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('never reaches the hooks for a caller that is not an agent', async () => {
|
|
75
|
+
const { gate, reads } = makeGate();
|
|
76
|
+
await notifyAgentRead(gate, ctx({ source: 'session' }), 'main', P_FILE);
|
|
77
|
+
expect(reads).toEqual([]);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('never reaches the hooks for the recovery bot, whatever the case of its address', async () => {
|
|
81
|
+
const { gate, reads } = makeGate();
|
|
82
|
+
await notifyAgentRead(gate, ctx({ user: { id: 'bot', email: 'Recovery-Bot@Bevel.local', name: 'bot' } }), 'main', P_FILE);
|
|
83
|
+
expect(reads).toEqual([]);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('refuses the read with the hook\'s own message and status', async () => {
|
|
87
|
+
const hooks = new WorkflowHooks();
|
|
88
|
+
hooks.onAgentRead(async () => {
|
|
89
|
+
throw new ToolError('Not in this conversation.', 403);
|
|
90
|
+
});
|
|
91
|
+
const gate: AgentAccessGate = { recoveryBotEmail: RECOVERY_EMAIL, hooks, notes: new ToolDescriptionNotes() };
|
|
92
|
+
await expect(notifyAgentRead(gate, ctx(), 'main', P_FILE)).rejects.toMatchObject({
|
|
93
|
+
message: 'Not in this conversation.',
|
|
94
|
+
status: 403,
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('is a no-op with no hook registered', async () => {
|
|
99
|
+
const gate: AgentAccessGate = {
|
|
100
|
+
recoveryBotEmail: RECOVERY_EMAIL,
|
|
101
|
+
hooks: new WorkflowHooks(),
|
|
102
|
+
notes: new ToolDescriptionNotes(),
|
|
103
|
+
};
|
|
104
|
+
await expect(notifyAgentRead(gate, ctx({ sessionId: undefined }), 'main', P_FILE)).resolves.toBeUndefined();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('assertAgentWriteAllowed', () => {
|
|
109
|
+
it('calls the write hook with the path, and never the read hook', async () => {
|
|
110
|
+
const { gate, reads, writes } = makeGate();
|
|
111
|
+
await assertAgentWriteAllowed(gate, ctx(), 'draft-1', P_FILE);
|
|
112
|
+
expect(reads).toEqual([]);
|
|
113
|
+
expect(writes).toHaveLength(1);
|
|
114
|
+
expect(writes[0]).toMatchObject({ sessionId: 's1', wsPath: P_FILE, branch: 'draft-1', source: 'external' });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('calls the write hook with NO path for a write-capable call that has none', async () => {
|
|
118
|
+
const { gate, writes } = makeGate();
|
|
119
|
+
await assertAgentWriteAllowed(gate, ctx(), 'main');
|
|
120
|
+
expect(writes).toHaveLength(1);
|
|
121
|
+
expect(writes[0].wsPath).toBeUndefined();
|
|
122
|
+
expect(writes[0].branch).toBe('main');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('refuses the write with the hook\'s own message and status', async () => {
|
|
126
|
+
const hooks = new WorkflowHooks();
|
|
127
|
+
hooks.onPreWrite(async () => {
|
|
128
|
+
throw new ToolError('Not in this conversation.', 403);
|
|
129
|
+
});
|
|
130
|
+
const gate: AgentAccessGate = { recoveryBotEmail: RECOVERY_EMAIL, hooks, notes: new ToolDescriptionNotes() };
|
|
131
|
+
await expect(assertAgentWriteAllowed(gate, ctx(), 'main', P_FILE)).rejects.toMatchObject({
|
|
132
|
+
message: 'Not in this conversation.',
|
|
133
|
+
status: 403,
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('runs every registered write hook, in registration order, until one throws', async () => {
|
|
138
|
+
const hooks = new WorkflowHooks();
|
|
139
|
+
const seen: string[] = [];
|
|
140
|
+
hooks.onPreWrite(async () => {
|
|
141
|
+
seen.push('first');
|
|
142
|
+
});
|
|
143
|
+
hooks.onPreWrite(async () => {
|
|
144
|
+
seen.push('second');
|
|
145
|
+
throw new ToolError('no', 403);
|
|
146
|
+
});
|
|
147
|
+
hooks.onPreWrite(async () => {
|
|
148
|
+
seen.push('third');
|
|
149
|
+
});
|
|
150
|
+
const gate: AgentAccessGate = { recoveryBotEmail: RECOVERY_EMAIL, hooks, notes: new ToolDescriptionNotes() };
|
|
151
|
+
await expect(assertAgentWriteAllowed(gate, ctx(), 'main', P_FILE)).rejects.toThrow('no');
|
|
152
|
+
expect(seen).toEqual(['first', 'second']);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('never reaches the hooks for a person in the app or for the recovery bot', async () => {
|
|
156
|
+
const { gate, writes } = makeGate();
|
|
157
|
+
await assertAgentWriteAllowed(gate, ctx({ source: 'session' }), 'main', P_FILE);
|
|
158
|
+
await assertAgentWriteAllowed(gate, ctx({ user: { id: 'bot', email: RECOVERY_EMAIL, name: 'bot' } }), 'main', P_FILE);
|
|
159
|
+
expect(writes).toEqual([]);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('is a no-op with no hook registered, with or without a session id', async () => {
|
|
163
|
+
const gate: AgentAccessGate = {
|
|
164
|
+
recoveryBotEmail: RECOVERY_EMAIL,
|
|
165
|
+
hooks: new WorkflowHooks(),
|
|
166
|
+
notes: new ToolDescriptionNotes(),
|
|
167
|
+
};
|
|
168
|
+
await expect(assertAgentWriteAllowed(gate, ctx(), 'main', P_FILE)).resolves.toBeUndefined();
|
|
169
|
+
await expect(assertAgentWriteAllowed(gate, ctx({ sessionId: undefined }), 'main', P_FILE)).resolves.toBeUndefined();
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe('ToolDescriptionNotes', () => {
|
|
174
|
+
let notes: ToolDescriptionNotes;
|
|
175
|
+
beforeEach(() => {
|
|
176
|
+
notes = new ToolDescriptionNotes();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('has no note registered by default, which is every Hexis-only deployment', () => {
|
|
180
|
+
expect(notes.gatedToolNote()).toBe('');
|
|
181
|
+
expect(notes.sessionIdDescription()).toBe(SESSION_ID_DESCRIPTION);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('appends a registered gated-tool note and a registered sessionId note', () => {
|
|
185
|
+
notes.registerGatedToolNote(' Stay put.');
|
|
186
|
+
notes.registerSessionIdNote(' It also pins the boundary.');
|
|
187
|
+
expect(notes.gatedToolNote()).toBe(' Stay put.');
|
|
188
|
+
expect(notes.sessionIdDescription()).toBe(`${SESSION_ID_DESCRIPTION} It also pins the boundary.`);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('tells its subscribers on every registration, so mounted tools re-describe', () => {
|
|
192
|
+
let calls = 0;
|
|
193
|
+
notes.onChange(() => {
|
|
194
|
+
calls++;
|
|
195
|
+
});
|
|
196
|
+
notes.registerGatedToolNote(' a');
|
|
197
|
+
notes.registerSessionIdNote(' b');
|
|
198
|
+
expect(calls).toBe(2);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('the default sessionId input says what the id is, and mentions no ontology', () => {
|
|
202
|
+
expect(SESSION_ID_INPUT.description).toBe(SESSION_ID_DESCRIPTION);
|
|
203
|
+
expect(SESSION_ID_DESCRIPTION).toMatch(/conversation/i);
|
|
204
|
+
expect(SESSION_ID_DESCRIPTION).toMatch(/start_session/);
|
|
205
|
+
expect(SESSION_ID_DESCRIPTION).toMatch(/ask/);
|
|
206
|
+
expect(SESSION_ID_DESCRIPTION.toLowerCase()).not.toContain('ontolog');
|
|
207
|
+
});
|
|
208
|
+
});
|