@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
|
@@ -2,6 +2,7 @@ import { sql } from 'drizzle-orm';
|
|
|
2
2
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
3
3
|
import pg from 'pg';
|
|
4
4
|
import * as schema from './schema.js';
|
|
5
|
+
import { PII_CIPHERTEXT_PREFIX, PiiParam, derivePiiKeys, type PiiKeys } from '../../shared/column-crypto.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* One database, one schema per knowledge base.
|
|
@@ -74,6 +75,83 @@ export interface DbOptions {
|
|
|
74
75
|
max?: number;
|
|
75
76
|
/** How long an idle connection is kept before the pool closes it. Default: `pg`'s. */
|
|
76
77
|
idleTimeoutMillis?: number;
|
|
78
|
+
/**
|
|
79
|
+
* The key this handle seals personal data with: the `SECRETS_ENC_KEY` of
|
|
80
|
+
* the knowledge base whose rows it holds (its own derived one, for a
|
|
81
|
+
* tenant). Without it the handle reads sealed values as their blobs and
|
|
82
|
+
* refuses to write one — right for a handle that only touches tables with
|
|
83
|
+
* no personal data, and a loud failure for one that was meant to have it.
|
|
84
|
+
*/
|
|
85
|
+
piiKey?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
type QueryFn = (this: pg.Client, config?: unknown, values?: unknown, callback?: unknown) => unknown;
|
|
89
|
+
type ResultCallback = (err: unknown, result: unknown) => void;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The `pg` client of a handle that holds `keys`: the one place personal data
|
|
93
|
+
* changes form. Going in, every value a personal-data column marked (see
|
|
94
|
+
* `column-crypto.ts`) is replaced by its ciphertext or its blind index.
|
|
95
|
+
* Coming out, every sealed value of a result is opened — by its prefix, not
|
|
96
|
+
* by its column, so raw SQL through the handle reads plaintext too; a
|
|
97
|
+
* statement that needs what is STORED asks for it as bytes
|
|
98
|
+
* (`convert_to(col, 'UTF8')`).
|
|
99
|
+
*
|
|
100
|
+
* At the client, because that is what the handle owns: the pool builds every
|
|
101
|
+
* connection from this class, a transaction's included, so there is no path
|
|
102
|
+
* from this handle to the database that goes round it. Results that are
|
|
103
|
+
* streamed to a `Submittable` (a cursor) are not opened; nothing here uses one.
|
|
104
|
+
*/
|
|
105
|
+
function keyedClient(keys: PiiKeys | null): typeof pg.Client {
|
|
106
|
+
const stored = (value: unknown): unknown => {
|
|
107
|
+
if (!(value instanceof PiiParam)) return value;
|
|
108
|
+
return keys ? value.stored(keys) : value.toPostgres();
|
|
109
|
+
};
|
|
110
|
+
const openCell = (value: unknown): unknown =>
|
|
111
|
+
typeof value === 'string' && value.startsWith(PII_CIPHERTEXT_PREFIX) ? keys!.read(value) : value;
|
|
112
|
+
const openOne = (result: unknown): void => {
|
|
113
|
+
const rows = (result as { rows?: unknown } | null)?.rows;
|
|
114
|
+
if (!Array.isArray(rows)) return;
|
|
115
|
+
for (const row of rows) {
|
|
116
|
+
if (Array.isArray(row)) {
|
|
117
|
+
for (let i = 0; i < row.length; i++) row[i] = openCell(row[i]);
|
|
118
|
+
} else if (row && typeof row === 'object') {
|
|
119
|
+
const record = row as Record<string, unknown>;
|
|
120
|
+
for (const column of Object.keys(record)) record[column] = openCell(record[column]);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const open = <T,>(result: T): T => {
|
|
125
|
+
if (!keys) return result;
|
|
126
|
+
// Several statements in one query string come back as several results.
|
|
127
|
+
if (Array.isArray(result)) result.forEach(openOne);
|
|
128
|
+
else openOne(result);
|
|
129
|
+
return result;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const base = pg.Client.prototype.query as unknown as QueryFn;
|
|
133
|
+
class KeyedClient extends pg.Client {}
|
|
134
|
+
const query: QueryFn = function (config, values, callback) {
|
|
135
|
+
if (typeof values === 'function') {
|
|
136
|
+
callback = values;
|
|
137
|
+
values = undefined;
|
|
138
|
+
}
|
|
139
|
+
if (Array.isArray(values)) {
|
|
140
|
+
values = values.map(stored);
|
|
141
|
+
} else if (config && typeof config === 'object' && Array.isArray((config as { values?: unknown }).values)) {
|
|
142
|
+
const withValues = config as { values: unknown[]; submit?: unknown };
|
|
143
|
+
if (typeof withValues.submit === 'function') withValues.values = withValues.values.map(stored);
|
|
144
|
+
else config = { ...withValues, values: withValues.values.map(stored) };
|
|
145
|
+
}
|
|
146
|
+
if (typeof callback === 'function') {
|
|
147
|
+
const done = callback as ResultCallback;
|
|
148
|
+
return base.call(this, config, values, (err: unknown, result: unknown) => done(err, err ? result : open(result)));
|
|
149
|
+
}
|
|
150
|
+
const pending = base.call(this, config, values);
|
|
151
|
+
return pending instanceof Promise ? pending.then(open) : pending;
|
|
152
|
+
};
|
|
153
|
+
Object.defineProperty(KeyedClient.prototype, 'query', { value: query, writable: true, configurable: true });
|
|
154
|
+
return KeyedClient;
|
|
77
155
|
}
|
|
78
156
|
|
|
79
157
|
/**
|
|
@@ -96,7 +174,11 @@ const CONNECT_TIMEOUT_MS = 30_000;
|
|
|
96
174
|
*/
|
|
97
175
|
export function createDb(databaseUrl: string, opts: DbOptions = {}) {
|
|
98
176
|
const dbSchema = assertSchemaName(opts.schema ?? DEFAULT_DB_SCHEMA);
|
|
177
|
+
// Only an ABSENT key means "this handle holds none". A blank or malformed
|
|
178
|
+
// one was meant to be a key, and is refused here rather than read as none.
|
|
179
|
+
const keys = opts.piiKey === undefined ? null : derivePiiKeys(opts.piiKey);
|
|
99
180
|
const pool = new pg.Pool({
|
|
181
|
+
Client: keyedClient(keys),
|
|
100
182
|
connectionString: databaseUrl,
|
|
101
183
|
connectionTimeoutMillis: CONNECT_TIMEOUT_MS,
|
|
102
184
|
max: opts.max,
|
|
@@ -109,6 +191,7 @@ export function createDb(databaseUrl: string, opts: DbOptions = {}) {
|
|
|
109
191
|
});
|
|
110
192
|
const db = drizzle(pool, { schema });
|
|
111
193
|
schemas.set(db, dbSchema);
|
|
194
|
+
if (keys) piiKeys.set(db, { keys, of: opts.piiKey! });
|
|
112
195
|
return db;
|
|
113
196
|
}
|
|
114
197
|
|
|
@@ -119,7 +202,22 @@ export function dbSchemaOf(db: Pick<Database, '$client'>): string {
|
|
|
119
202
|
return schemas.get(db as Database) ?? DEFAULT_DB_SCHEMA;
|
|
120
203
|
}
|
|
121
204
|
|
|
205
|
+
/**
|
|
206
|
+
* The keys a handle seals personal data with — for the few callers that
|
|
207
|
+
* handle the stored form themselves (the backfill at start; an overlay's own
|
|
208
|
+
* SQL). Everything else never sees a key: the column types and the handle's
|
|
209
|
+
* connection do the work. Throws for a handle built without one.
|
|
210
|
+
*/
|
|
211
|
+
export function piiKeysOf(db: Pick<Database, '$client'>): PiiKeys {
|
|
212
|
+
const held = piiKeys.get(db as Database);
|
|
213
|
+
if (!held) {
|
|
214
|
+
throw new Error('This database handle holds no personal-data key: build it with createDb/getDb and its piiKey.');
|
|
215
|
+
}
|
|
216
|
+
return held.keys;
|
|
217
|
+
}
|
|
218
|
+
|
|
122
219
|
const schemas = new WeakMap<Database, string>();
|
|
220
|
+
const piiKeys = new WeakMap<Database, { keys: PiiKeys; of: string }>();
|
|
123
221
|
const cache = new Map<string, Database>();
|
|
124
222
|
|
|
125
223
|
const cacheKey = (databaseUrl: string, dbSchema: string) => `${dbSchema}\u0000${databaseUrl}`;
|
|
@@ -136,10 +234,29 @@ export function getDb(databaseUrl: string, opts: DbOptions = {}): Database {
|
|
|
136
234
|
if (!db) {
|
|
137
235
|
db = createDb(databaseUrl, opts);
|
|
138
236
|
cache.set(key, db);
|
|
237
|
+
} else if (opts.piiKey !== undefined && !holdsKey(db, opts.piiKey)) {
|
|
238
|
+
// One schema is one knowledge base and one key. Handing back the cached
|
|
239
|
+
// pool would seal this caller's rows under the other caller's key.
|
|
240
|
+
throw new Error(`The database handle for schema "${dbSchema}" is already open with a different personal-data key.`);
|
|
139
241
|
}
|
|
140
242
|
return db;
|
|
141
243
|
}
|
|
142
244
|
|
|
245
|
+
/**
|
|
246
|
+
* Whether `db` was opened with the key `piiKey` spells. The KEY, not its
|
|
247
|
+
* spelling: `SECRETS_ENC_KEY` may be written as hex or as base64, and one
|
|
248
|
+
* process can be handed the same 32 bytes both ways — refusing the second as
|
|
249
|
+
* "a different key" would refuse a caller that holds the right one. Compared
|
|
250
|
+
* through what the two derive, so no key material is held here to compare.
|
|
251
|
+
*/
|
|
252
|
+
function holdsKey(db: Database, piiKey: string): boolean {
|
|
253
|
+
const held = piiKeys.get(db);
|
|
254
|
+
if (!held) return false;
|
|
255
|
+
if (held.of === piiKey) return true;
|
|
256
|
+
const probe = 'same-key?';
|
|
257
|
+
return held.keys.index(probe) === derivePiiKeys(piiKey).index(probe);
|
|
258
|
+
}
|
|
259
|
+
|
|
143
260
|
/**
|
|
144
261
|
* End a handle's pool and forget it, so a later {@link getDb} for the same
|
|
145
262
|
* schema opens a fresh one rather than handing out a pool that was ended.
|
|
@@ -1,23 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CORE platform tables — the git-backed workspace/workflow, auth, access,
|
|
3
|
-
* change requests, locks, the pending-commits queue,
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* change requests, locks, the pending-commits queue, connection keys, MCP
|
|
4
|
+
* OAuth, and the Secrets Vault. A core-only deployment migrates and runs on
|
|
5
|
+
* exactly these tables.
|
|
6
6
|
*
|
|
7
7
|
* Enterprise-only tables (chat, routines, watchlist, connectors, LLM config,
|
|
8
8
|
* SharePoint/Atlassian links, feedback, upload, kb-revalidation) live in
|
|
9
9
|
* `enterprise-schema.ts`, which imports the FK targets (`users`,
|
|
10
10
|
* `externalApiKeys`) from here. `schema.ts` re-exports both, so existing
|
|
11
11
|
* imports keep working unchanged.
|
|
12
|
+
*
|
|
13
|
+
* PII columns (emails, display names, change-request/comment text) are
|
|
14
|
+
* `encryptedText` — AES-256-GCM ciphertext in the database, transparently
|
|
15
|
+
* decrypted on read (see `shared/column-crypto.ts`). Because the ciphertext is
|
|
16
|
+
* randomized, equality lookups and unique constraints on those columns go
|
|
17
|
+
* through their deterministic `*_bidx` companions (HMAC-SHA256 blind indexes,
|
|
18
|
+
* `blindIndexText`) — never `eq()` an encrypted column directly. A `*_bidx`
|
|
19
|
+
* column is written and compared WITH THE ADDRESS ITSELF; the database handle
|
|
20
|
+
* the statement runs on turns it into the index, under that handle's key.
|
|
12
21
|
*/
|
|
13
22
|
import { sql } from 'drizzle-orm';
|
|
14
23
|
import { boolean, check, index, integer, jsonb, pgTable, primaryKey, text, timestamp, uniqueIndex, uuid } from 'drizzle-orm/pg-core';
|
|
24
|
+
import { blindIndexText, encryptedText } from '../../shared/column-crypto.js';
|
|
15
25
|
|
|
16
26
|
export const users = pgTable('users', {
|
|
17
27
|
id: uuid('id').defaultRandom().primaryKey(),
|
|
18
|
-
email:
|
|
19
|
-
|
|
20
|
-
|
|
28
|
+
email: encryptedText('email').notNull(),
|
|
29
|
+
/**
|
|
30
|
+
* Blind index of the lowercased email — carries the uniqueness constraint
|
|
31
|
+
* and every lookup-by-email (login, upsert conflict target), which the
|
|
32
|
+
* randomized `email` ciphertext cannot. Write the email into it on every
|
|
33
|
+
* insert, and compare it with the email.
|
|
34
|
+
*/
|
|
35
|
+
emailBidx: blindIndexText('email_bidx').notNull(),
|
|
36
|
+
name: encryptedText('name').notNull(),
|
|
37
|
+
avatarUrl: encryptedText('avatar_url'),
|
|
21
38
|
/**
|
|
22
39
|
* scrypt hash for password login (see auth/password-hash.ts). NULL for
|
|
23
40
|
* accounts that only ever signed in via SSO — password login refuses them
|
|
@@ -37,9 +54,24 @@ export const users = pgTable('users', {
|
|
|
37
54
|
* backfill would need, and being shown the setup once costs a click.
|
|
38
55
|
*/
|
|
39
56
|
onboardingDone: boolean('onboarding_done').default(false).notNull(),
|
|
57
|
+
/**
|
|
58
|
+
* When an admin switched this account off; NULL while it is on. A
|
|
59
|
+
* deactivated account keeps its row, its history and its place in roles
|
|
60
|
+
* and groups, but nothing it holds is honoured: it cannot sign in, and its
|
|
61
|
+
* session, connection keys, agent tokens and internal tokens are refused
|
|
62
|
+
* the next time they are presented (see `AuthService.isActive`). Turning it
|
|
63
|
+
* back on restores all of them as they were.
|
|
64
|
+
*
|
|
65
|
+
* The off switch a host that sells seats needs — a seat is an account that
|
|
66
|
+
* is on — and the one an admin uses for someone who left without erasing
|
|
67
|
+
* what they did.
|
|
68
|
+
*/
|
|
69
|
+
deactivatedAt: timestamp('deactivated_at'),
|
|
40
70
|
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
41
71
|
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
|
42
|
-
})
|
|
72
|
+
}, (t) => ({
|
|
73
|
+
emailBidxUnq: uniqueIndex('users_email_bidx_unq').on(t.emailBidx),
|
|
74
|
+
}));
|
|
43
75
|
|
|
44
76
|
/**
|
|
45
77
|
* Per-file owner approvals on a PR. A PR is mergeable-in-Bevel when every
|
|
@@ -56,15 +88,18 @@ export const prFileApprovals = pgTable('pr_file_approvals', {
|
|
|
56
88
|
id: uuid('id').defaultRandom().primaryKey(),
|
|
57
89
|
prNumber: integer('pr_number').notNull(),
|
|
58
90
|
path: text('path').notNull(),
|
|
59
|
-
approverEmail:
|
|
60
|
-
|
|
91
|
+
approverEmail: encryptedText('approver_email').notNull(), // lowercased at insert
|
|
92
|
+
/** Blind index of `approver_email` — the uniqueness key and eq() lookup. */
|
|
93
|
+
approverEmailBidx: blindIndexText('approver_email_bidx').notNull(),
|
|
94
|
+
approverName: encryptedText('approver_name').notNull(),
|
|
61
95
|
headSha: text('head_sha').notNull(),
|
|
62
96
|
approvedAt: timestamp('approved_at').defaultNow().notNull(),
|
|
63
97
|
}, (t) => ({
|
|
64
98
|
// Idempotency: approving the same path on the same SHA twice must be a no-op,
|
|
65
|
-
// not a duplicate row. Unique on the (PR, path, approver, headSha) tuple
|
|
66
|
-
|
|
67
|
-
|
|
99
|
+
// not a duplicate row. Unique on the (PR, path, approver, headSha) tuple —
|
|
100
|
+
// via the blind index, because the encrypted email is randomized.
|
|
101
|
+
unq: uniqueIndex('pr_file_approvals_bidx_unq')
|
|
102
|
+
.on(t.prNumber, t.path, t.approverEmailBidx, t.headSha),
|
|
68
103
|
byPr: index('pr_file_approvals_by_pr').on(t.prNumber),
|
|
69
104
|
}));
|
|
70
105
|
|
|
@@ -78,12 +113,15 @@ export const prFileApprovals = pgTable('pr_file_approvals', {
|
|
|
78
113
|
export const prMergeLog = pgTable('pr_merge_log', {
|
|
79
114
|
id: uuid('id').defaultRandom().primaryKey(),
|
|
80
115
|
prNumber: integer('pr_number').notNull(),
|
|
81
|
-
triggeredByEmail:
|
|
82
|
-
|
|
116
|
+
triggeredByEmail: encryptedText('triggered_by_email').notNull(),
|
|
117
|
+
/** Blind index of `triggered_by_email` — GDPR-erasure lookup. */
|
|
118
|
+
triggeredByEmailBidx: blindIndexText('triggered_by_email_bidx').notNull(),
|
|
119
|
+
triggeredByName: encryptedText('triggered_by_name').notNull(),
|
|
83
120
|
headShaAtMerge: text('head_sha_at_merge').notNull(),
|
|
84
121
|
mergeMethod: text('merge_method').notNull(),
|
|
85
122
|
succeeded: boolean('succeeded').notNull(),
|
|
86
|
-
|
|
123
|
+
// Encrypted: merge failure output can quote author identities and CR text.
|
|
124
|
+
error: encryptedText('error'),
|
|
87
125
|
startedAt: timestamp('started_at').defaultNow().notNull(),
|
|
88
126
|
completedAt: timestamp('completed_at'),
|
|
89
127
|
}, (t) => ({
|
|
@@ -107,12 +145,15 @@ export const prMergeLog = pgTable('pr_merge_log', {
|
|
|
107
145
|
export const prComments = pgTable('pr_comments', {
|
|
108
146
|
id: uuid('id').defaultRandom().primaryKey(),
|
|
109
147
|
prNumber: integer('pr_number').notNull(),
|
|
110
|
-
authorEmail:
|
|
111
|
-
|
|
148
|
+
authorEmail: encryptedText('author_email').notNull(),
|
|
149
|
+
/** Blind index of `author_email` — GDPR-erasure lookup. */
|
|
150
|
+
authorEmailBidx: blindIndexText('author_email_bidx').notNull(),
|
|
151
|
+
authorName: encryptedText('author_name').notNull(),
|
|
112
152
|
path: text('path'),
|
|
113
153
|
line: integer('line'),
|
|
114
154
|
headSha: text('head_sha').notNull(),
|
|
115
|
-
|
|
155
|
+
// Encrypted: review conversation exists only in this DB (never on GitHub).
|
|
156
|
+
body: encryptedText('body').notNull(),
|
|
116
157
|
parentId: uuid('parent_id'),
|
|
117
158
|
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
118
159
|
updatedAt: timestamp('updated_at'),
|
|
@@ -141,25 +182,53 @@ export const changeRequests = pgTable('change_requests', {
|
|
|
141
182
|
number: integer('number').generatedAlwaysAsIdentity(),
|
|
142
183
|
sourceBranch: text('source_branch').notNull(),
|
|
143
184
|
targetBranch: text('target_branch').notNull(),
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
185
|
+
// Encrypted: CR title/body exist only in this DB (the remote sees branches).
|
|
186
|
+
// The DB-level default '' bypasses toDriver, which is fine: the empty string
|
|
187
|
+
// is deliberately stored unencrypted (see encryptPii), so a defaulted row
|
|
188
|
+
// and an app-written empty body hold the identical representation.
|
|
189
|
+
title: encryptedText('title').notNull(),
|
|
190
|
+
body: encryptedText('body').notNull().default(''),
|
|
191
|
+
authorEmail: encryptedText('author_email').notNull(), // lowercased at insert
|
|
192
|
+
/** Blind index of `author_email` — GDPR-erasure lookup. */
|
|
193
|
+
authorEmailBidx: blindIndexText('author_email_bidx').notNull(),
|
|
194
|
+
authorName: encryptedText('author_name').notNull(),
|
|
148
195
|
state: text('state').notNull().default('open'), // 'open' | 'merged' | 'closed'
|
|
149
196
|
mergedSha: text('merged_sha'),
|
|
150
197
|
// The last apply attempt that did not land (null when none, or once a gate
|
|
151
198
|
// input it depended on changed). Persisted rather than only pushed to the
|
|
152
199
|
// clicker so every viewer of the still-open request — its author first —
|
|
153
200
|
// sees the refusal.
|
|
154
|
-
|
|
201
|
+
// Encrypted: the reason can quote identities the way the merge log's error
|
|
202
|
+
// does, and the name is a person's.
|
|
203
|
+
applyFailureReason: encryptedText('apply_failure_reason'),
|
|
155
204
|
applyFailureConflicts: boolean('apply_failure_conflicts'),
|
|
156
205
|
applyFailedAt: timestamp('apply_failed_at'),
|
|
157
|
-
applyFailedByName:
|
|
206
|
+
applyFailedByName: encryptedText('apply_failed_by_name'),
|
|
207
|
+
/**
|
|
208
|
+
* Blind index of the email of the person named above — what account erasure
|
|
209
|
+
* finds their name by. Written and cleared with the name, so a name never
|
|
210
|
+
* stands without it: a refusal recorded before the encryption release kept
|
|
211
|
+
* no address, and its name is taken off at the first start on this release
|
|
212
|
+
* (`clearUnindexedRefusalNames` in `migrate.ts`).
|
|
213
|
+
*/
|
|
214
|
+
applyFailedByEmailBidx: blindIndexText('apply_failed_by_email_bidx'),
|
|
158
215
|
/** What refused the last apply: 'gate' (approvals), 'conflicts' (git), 'error' (anything else). */
|
|
159
216
|
applyFailureKind: text('apply_failure_kind'),
|
|
160
217
|
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
161
218
|
updatedAt: timestamp('updated_at'),
|
|
162
219
|
closedAt: timestamp('closed_at'),
|
|
220
|
+
/**
|
|
221
|
+
* Why a closed request was closed, when something other than a person
|
|
222
|
+
* closed it. Today the one value is 'repository-replaced': the admin
|
|
223
|
+
* pointed the deployment at a different knowledge-base repository and chose
|
|
224
|
+
* to close the requests rather than keep them. Null for every request
|
|
225
|
+
* closed the ordinary way — by its author, or by the deleted-branch sweep —
|
|
226
|
+
* so the column reads as "nobody recorded a reason", not as a default.
|
|
227
|
+
*
|
|
228
|
+
* The row is never deleted: a replaced repository loses the branches, not
|
|
229
|
+
* the record of what people asked for.
|
|
230
|
+
*/
|
|
231
|
+
closedReason: text('closed_reason'),
|
|
163
232
|
}, (t) => ({
|
|
164
233
|
numberUnq: uniqueIndex('change_requests_number_unq').on(t.number),
|
|
165
234
|
// Enforce the "A→B blocks A→B while open" uniqueness rule at the DB level —
|
|
@@ -245,7 +314,7 @@ export const fileLocks = pgTable('file_locks', {
|
|
|
245
314
|
branch: text('branch').notNull(),
|
|
246
315
|
path: text('path').notNull(),
|
|
247
316
|
holderUserId: uuid('holder_user_id').notNull().references(() => users.id),
|
|
248
|
-
holderName:
|
|
317
|
+
holderName: encryptedText('holder_name').notNull(),
|
|
249
318
|
// How the lock was acquired. 'edit' (the default) is a normal write hold —
|
|
250
319
|
// the holder is editing and the release publishes their bytes.
|
|
251
320
|
// 'coordination' is a pure-mutex hold (see IWorkflowService.acquireLock)
|
|
@@ -306,8 +375,10 @@ export const pendingCommits = pgTable('pending_commits', {
|
|
|
306
375
|
// Author attribution captured at enqueue. The denormalised name/email pair
|
|
307
376
|
// lands on the eventual `git commit --author=` so the commit history shows
|
|
308
377
|
// the human who triggered the save, not the worker.
|
|
309
|
-
authorEmail:
|
|
310
|
-
|
|
378
|
+
authorEmail: encryptedText('author_email').notNull(), // lowercased at insert
|
|
379
|
+
/** Blind index of `author_email` — GDPR-erasure lookup. */
|
|
380
|
+
authorEmailBidx: blindIndexText('author_email_bidx').notNull(),
|
|
381
|
+
authorName: encryptedText('author_name').notNull(),
|
|
311
382
|
queuedAt: timestamp('queued_at').defaultNow().notNull(),
|
|
312
383
|
// `running` is set while the worker is mid-commit so a second worker
|
|
313
384
|
// (e.g. after a restart with another instance still draining) doesn't
|
|
@@ -320,7 +391,8 @@ export const pendingCommits = pgTable('pending_commits', {
|
|
|
320
391
|
// `needs_attention` + feedback notice instead of looping forever.
|
|
321
392
|
recoveryAgentRuns: integer('recovery_agent_runs').notNull().default(0),
|
|
322
393
|
lastAttemptedAt: timestamp('last_attempted_at'),
|
|
323
|
-
|
|
394
|
+
// Encrypted: git/agent failure output can quote author identities and paths.
|
|
395
|
+
lastError: encryptedText('last_error'),
|
|
324
396
|
}, (t) => ({
|
|
325
397
|
// Primary read pattern: drain in queued order, scoped to a workspace.
|
|
326
398
|
byWorkspaceQueued: index('pending_commits_by_workspace_queued').on(t.workspaceId, t.queuedAt),
|
|
@@ -382,30 +454,6 @@ export const secrets = pgTable('secrets', {
|
|
|
382
454
|
sharedKeyUnq: uniqueIndex('secrets_shared_key_unq').on(t.key).where(sql`${t.userId} is null`),
|
|
383
455
|
}));
|
|
384
456
|
|
|
385
|
-
/**
|
|
386
|
-
* Ontology-session boundary — the per-session log of which named ontologies a
|
|
387
|
-
* run has TOUCHED (one row per `(session_id, ontology)`). Each agent operation
|
|
388
|
-
* that resolves to a named ontology records a row here (idempotent on the PK).
|
|
389
|
-
*
|
|
390
|
-
* The rule the gate enforces from this set:
|
|
391
|
-
* - READS are always allowed; they just record the ontology they touched.
|
|
392
|
-
* - WRITES are allowed only while the session's touched set is a SINGLE named
|
|
393
|
-
* ontology equal to the write target. Once a session has touched two or
|
|
394
|
-
* more ontologies (e.g. by reading across them), every write is blocked —
|
|
395
|
-
* "read across ontologies → you can't write anymore at all."
|
|
396
|
-
*
|
|
397
|
-
* Postgres is the durable source of truth so the boundary holds across a
|
|
398
|
-
* backend restart (an in-memory set would reset and silently un-poison a run).
|
|
399
|
-
* A write-through in-process cache fronts this table for the chatty read path;
|
|
400
|
-
* a cache miss falls back here.
|
|
401
|
-
*
|
|
402
|
-
* - PK is `(session_id, ontology)` — a set, idempotent on repeat touches.
|
|
403
|
-
* - `ontology` is the resolved ontology id (e.g. `KnowledgeBase/Product`).
|
|
404
|
-
* Neutral touches don't record, so they never write a row.
|
|
405
|
-
* - `touched_at` drives the abandoned-run `sweepOlderThan` backstop; the
|
|
406
|
-
* primary reclamation is an explicit delete of all of a session's rows at
|
|
407
|
-
* run end.
|
|
408
|
-
*/
|
|
409
457
|
/**
|
|
410
458
|
* OAuth clients dynamically registered by MCP clients (RFC 7591 DCR). When an
|
|
411
459
|
* MCP client (claude.ai, Claude Code) connects to `/api/mcp` without a
|
|
@@ -575,6 +623,13 @@ export const agentEvents = pgTable('agent_events', {
|
|
|
575
623
|
),
|
|
576
624
|
}));
|
|
577
625
|
|
|
626
|
+
/**
|
|
627
|
+
* UNUSED IN THIS RELEASE. It backed the per-session boundary that a deployment
|
|
628
|
+
* now owns for itself, behind the agent read/write hooks; nothing in this
|
|
629
|
+
* package reads or writes it any more. The definition stays for ONE release so
|
|
630
|
+
* that no deployment runs a version in which neither side has the table, and
|
|
631
|
+
* the NEXT release drops it (see `TODOS.md`). Do not build on it.
|
|
632
|
+
*/
|
|
578
633
|
export const sessionOntologyTouches = pgTable('session_ontology_touches', {
|
|
579
634
|
sessionId: text('session_id').notNull(),
|
|
580
635
|
ontology: text('ontology').notNull(),
|
|
@@ -710,13 +765,15 @@ export const githubFacadeCodes = pgTable('github_facade_codes', {
|
|
|
710
765
|
*/
|
|
711
766
|
export const pluginJoinRequests = pgTable('plugin_join_requests', {
|
|
712
767
|
id: uuid('id').defaultRandom().primaryKey(),
|
|
713
|
-
requesterEmail:
|
|
768
|
+
requesterEmail: encryptedText('requester_email').notNull(), // lowercased at insert
|
|
769
|
+
/** Blind index of `requester_email` — the uniqueness key, the by-requester read and the erasure delete. */
|
|
770
|
+
requesterEmailBidx: blindIndexText('requester_email_bidx').notNull(),
|
|
714
771
|
/** Denormalised for the commit/change-request authorship, like `file_locks.holder_name`. */
|
|
715
|
-
requesterName:
|
|
772
|
+
requesterName: encryptedText('requester_name').notNull(),
|
|
716
773
|
pluginKey: text('plugin_key').notNull(),
|
|
717
774
|
status: text('status').notNull().default('pending'),
|
|
718
|
-
/** What the git work said when it refused — shown to the requester verbatim. */
|
|
719
|
-
failureReason:
|
|
775
|
+
/** What the git work said when it refused — shown to the requester verbatim. Encrypted: git quotes identities. */
|
|
776
|
+
failureReason: encryptedText('failure_reason'),
|
|
720
777
|
changeRequestNumber: integer('change_request_number'),
|
|
721
778
|
/**
|
|
722
779
|
* When a process took this row's git work, and the whole of the mutual
|
|
@@ -762,8 +819,9 @@ export const pluginJoinRequests = pgTable('plugin_join_requests', {
|
|
|
762
819
|
}, (t) => ({
|
|
763
820
|
// One request per person per plugin — the DB's rule, not a caller's. Also
|
|
764
821
|
// the index the plugin listing's by-requester read is served from.
|
|
765
|
-
|
|
766
|
-
|
|
822
|
+
// Via the blind index, because the encrypted email is randomized.
|
|
823
|
+
requesterPluginUnq: uniqueIndex('plugin_join_requests_requester_bidx_plugin_unq')
|
|
824
|
+
.on(t.requesterEmailBidx, t.pluginKey),
|
|
767
825
|
// The boot sweep: every row still `pending`, without a full scan.
|
|
768
826
|
byStatus: index('plugin_join_requests_by_status').on(t.status),
|
|
769
827
|
statusCheck: check(
|