@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
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import { testKbContext } from '../../../__tests__/kb-context.js';
|
|
3
|
-
|
|
4
|
-
import { SessionOntologyService } from '../session-ontology.service.js';
|
|
5
|
-
import type { Database } from '../../database/connection.js';
|
|
6
|
-
|
|
7
|
-
const PRODUCT = 'KnowledgeBase/Product';
|
|
8
|
-
const PLATFORM = 'KnowledgeBase/Platform';
|
|
9
|
-
const P_READ = 'KnowledgeBase/Product/Knowledge/Foo.md';
|
|
10
|
-
const PL_READ = 'KnowledgeBase/Platform/Knowledge/Bar.md';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Faithful in-memory fake of the slice of the Drizzle `Database` the service
|
|
14
|
-
* uses against the `session_ontology_touches` table (one row per
|
|
15
|
-
* `(session_id, ontology)`):
|
|
16
|
-
* - `insert().onConflictDoNothing()` adds a row unless the PK pair exists.
|
|
17
|
-
* - `select({ontology}).from().where(eq(session))` returns the session's rows.
|
|
18
|
-
* - `delete().where(eq(session) | lt(touchedAt, cutoff))` removes rows.
|
|
19
|
-
*
|
|
20
|
-
* A live-Postgres integration test (real PK conflict, real `touched_at < now()`
|
|
21
|
-
* sweep) is still owed once the repo grows a Postgres test harness (tasks 3.4).
|
|
22
|
-
*/
|
|
23
|
-
interface Row {
|
|
24
|
-
sessionId: string;
|
|
25
|
-
ontology: string;
|
|
26
|
-
touchedAt: Date;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function makeFakeDb() {
|
|
30
|
-
const rows: Row[] = [];
|
|
31
|
-
const db = {
|
|
32
|
-
_rows: rows,
|
|
33
|
-
insert() {
|
|
34
|
-
return {
|
|
35
|
-
values(v: { sessionId: string; ontology: string }) {
|
|
36
|
-
return {
|
|
37
|
-
onConflictDoNothing() {
|
|
38
|
-
const exists = rows.some((r) => r.sessionId === v.sessionId && r.ontology === v.ontology);
|
|
39
|
-
if (!exists) rows.push({ sessionId: v.sessionId, ontology: v.ontology, touchedAt: new Date() });
|
|
40
|
-
return Promise.resolve([]);
|
|
41
|
-
},
|
|
42
|
-
};
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
|
-
},
|
|
46
|
-
select() {
|
|
47
|
-
return {
|
|
48
|
-
from() {
|
|
49
|
-
return {
|
|
50
|
-
async where(pred: { sessionId: string }) {
|
|
51
|
-
return rows
|
|
52
|
-
.filter((r) => r.sessionId === pred.sessionId)
|
|
53
|
-
.map((r) => ({ ontology: r.ontology }));
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
},
|
|
59
|
-
delete() {
|
|
60
|
-
return {
|
|
61
|
-
where(pred: { sessionId?: string; cutoff?: Date }) {
|
|
62
|
-
if (pred.sessionId !== undefined) {
|
|
63
|
-
for (let i = rows.length - 1; i >= 0; i--) {
|
|
64
|
-
if (rows[i].sessionId === pred.sessionId) rows.splice(i, 1);
|
|
65
|
-
}
|
|
66
|
-
return { async returning() { return []; } } as never;
|
|
67
|
-
}
|
|
68
|
-
const removed: { sessionId: string }[] = [];
|
|
69
|
-
for (let i = rows.length - 1; i >= 0; i--) {
|
|
70
|
-
if (pred.cutoff && rows[i].touchedAt < pred.cutoff) {
|
|
71
|
-
removed.push({ sessionId: rows[i].sessionId });
|
|
72
|
-
rows.splice(i, 1);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return { async returning() { return removed; } } as never;
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
},
|
|
79
|
-
};
|
|
80
|
-
return db;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
vi.mock('drizzle-orm', async (importOriginal) => {
|
|
84
|
-
const actual = await importOriginal<typeof import('drizzle-orm')>();
|
|
85
|
-
return {
|
|
86
|
-
...actual,
|
|
87
|
-
eq: (_col: unknown, val: string) => ({ sessionId: val }),
|
|
88
|
-
lt: (_col: unknown, val: Date) => ({ cutoff: val }),
|
|
89
|
-
};
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
function makeService() {
|
|
93
|
-
const db = makeFakeDb();
|
|
94
|
-
return { svc: new SessionOntologyService(db as unknown as Database, testKbContext()), db };
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
describe('SessionOntologyService (touched-set model)', () => {
|
|
98
|
-
let svc: SessionOntologyService;
|
|
99
|
-
let db: ReturnType<typeof makeFakeDb>;
|
|
100
|
-
|
|
101
|
-
beforeEach(() => {
|
|
102
|
-
({ svc, db } = makeService());
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('records a read and allows it', async () => {
|
|
106
|
-
const d = await svc.checkOperation('s1', P_READ, false);
|
|
107
|
-
expect(d.allow).toBe(true);
|
|
108
|
-
expect(await svc.getTouched('s1')).toEqual([PRODUCT]);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('allows reads ACROSS ontologies (reads are never blocked)', async () => {
|
|
112
|
-
expect((await svc.checkOperation('s1', P_READ, false)).allow).toBe(true);
|
|
113
|
-
expect((await svc.checkOperation('s1', PL_READ, false)).allow).toBe(true);
|
|
114
|
-
expect(await svc.getTouched('s1')).toEqual([PLATFORM, PRODUCT]);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('allows a write while the session is confined to that one ontology', async () => {
|
|
118
|
-
await svc.checkOperation('s1', P_READ, false);
|
|
119
|
-
expect((await svc.checkOperation('s1', P_READ, true)).allow).toBe(true);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('allows the first write with no prior reads and pins to it', async () => {
|
|
123
|
-
expect((await svc.checkOperation('s1', P_READ, true)).allow).toBe(true);
|
|
124
|
-
expect(await svc.getTouched('s1')).toEqual([PRODUCT]);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('blocks a write to a different ontology than the one read (different-ontology)', async () => {
|
|
128
|
-
// Load-bearing for the start_session↔ask unification: the touched-set is
|
|
129
|
-
// keyed purely by sessionId, so a READ and a later WRITE that share ONE id
|
|
130
|
-
// hit the same set and cross-ontology writes are refused. This only protects
|
|
131
|
-
// the read→ask flow because both now run under the SAME id (start_session
|
|
132
|
-
// mints a real chat thread; ask accepts it). If a refactor re-splits those
|
|
133
|
-
// namespaces, the read and the ask write land in different touched-sets and
|
|
134
|
-
// this guarantee silently evaporates — see workspace.tools.ts start_session.
|
|
135
|
-
await svc.checkOperation('s1', P_READ, false);
|
|
136
|
-
const d = await svc.checkOperation('s1', PL_READ, true);
|
|
137
|
-
expect(d).toMatchObject({ allow: false, reason: 'different-ontology', attempted: PLATFORM });
|
|
138
|
-
// The blocked write does NOT record Platform.
|
|
139
|
-
expect(await svc.getTouched('s1')).toEqual([PRODUCT]);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('blocks ALL writes once the session has read across two ontologies (multi-ontology)', async () => {
|
|
143
|
-
await svc.checkOperation('s1', P_READ, false);
|
|
144
|
-
await svc.checkOperation('s1', PL_READ, false); // reads across — poisons writes
|
|
145
|
-
// Even a write back into Product is now blocked.
|
|
146
|
-
const back = await svc.checkOperation('s1', P_READ, true);
|
|
147
|
-
expect(back).toMatchObject({ allow: false, reason: 'multi-ontology' });
|
|
148
|
-
const other = await svc.checkOperation('s1', PL_READ, true);
|
|
149
|
-
expect(other.allow).toBe(false);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('treats a neutral path as always-allowed and never recorded', async () => {
|
|
153
|
-
const d = await svc.checkOperation('s1', 'access.md', true);
|
|
154
|
-
expect(d.allow).toBe(true);
|
|
155
|
-
expect(await svc.getTouched('s1')).toEqual([]);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('survives a "restart": a new instance re-reads the touched set from the store', async () => {
|
|
159
|
-
await svc.checkOperation('s1', P_READ, false);
|
|
160
|
-
await svc.checkOperation('s1', PL_READ, false);
|
|
161
|
-
const afterRestart = new SessionOntologyService(db as unknown as Database, testKbContext());
|
|
162
|
-
// The poison persists across the restart: writes still blocked.
|
|
163
|
-
expect((await afterRestart.checkOperation('s1', P_READ, true)).allow).toBe(false);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('reclaims a session on delete so it can write again', async () => {
|
|
167
|
-
await svc.checkOperation('s1', P_READ, false);
|
|
168
|
-
await svc.checkOperation('s1', PL_READ, false);
|
|
169
|
-
await svc.delete('s1');
|
|
170
|
-
expect(await svc.getTouched('s1')).toEqual([]);
|
|
171
|
-
expect((await svc.checkOperation('s1', P_READ, true)).allow).toBe(true);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it('sweeps abandoned sessions older than the cutoff', async () => {
|
|
175
|
-
await svc.checkOperation('old', P_READ, false);
|
|
176
|
-
db._rows.forEach((r) => { if (r.sessionId === 'old') r.touchedAt = new Date(Date.now() - 60_000); });
|
|
177
|
-
const removed = await svc.sweepOlderThan(new Date(Date.now() - 30_000));
|
|
178
|
-
expect(removed).toBe(1);
|
|
179
|
-
expect(await svc.getTouched('old')).toEqual([]);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it('records each ontology only once (idempotent touches)', async () => {
|
|
183
|
-
await svc.checkOperation('s1', P_READ, false);
|
|
184
|
-
await svc.checkOperation('s1', P_READ, false);
|
|
185
|
-
await svc.checkOperation('s1', P_READ, false);
|
|
186
|
-
expect(db._rows.filter((r) => r.sessionId === 's1').length).toBe(1);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it('serializes two concurrent same-session writes to different ontologies (only one wins)', async () => {
|
|
190
|
-
// Without per-session serialization both could observe an empty touched set
|
|
191
|
-
// and both pin to a different ontology before either INSERT lands.
|
|
192
|
-
const [a, b] = await Promise.all([
|
|
193
|
-
svc.checkOperation('s1', P_READ, true), // write → Product
|
|
194
|
-
svc.checkOperation('s1', PL_READ, true), // write → Platform (concurrent)
|
|
195
|
-
]);
|
|
196
|
-
// Exactly one write is allowed; the loser is blocked as different-ontology.
|
|
197
|
-
expect([a, b].filter((d) => d.allow)).toHaveLength(1);
|
|
198
|
-
// Only the winner's ontology is recorded.
|
|
199
|
-
expect(await svc.getTouched('s1')).toHaveLength(1);
|
|
200
|
-
});
|
|
201
|
-
});
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ontology-session boundary — the PURE decision core (no IO, no DB).
|
|
3
|
-
*
|
|
4
|
-
* Model: each run/session accumulates the SET of named ontologies it has
|
|
5
|
-
* touched (via any read or write). The rule is asymmetric:
|
|
6
|
-
*
|
|
7
|
-
* READS — always allowed. A read that touches a named ontology RECORDS it
|
|
8
|
-
* into the session's touched set (so it can poison later writes).
|
|
9
|
-
* WRITES — allowed only while the session is confined to a SINGLE named
|
|
10
|
-
* ontology and the write targets that same ontology. The moment a
|
|
11
|
-
* session has touched two or more named ontologies (typically by
|
|
12
|
-
* reading across them), EVERY subsequent write is blocked — "read
|
|
13
|
-
* across ontologies → you can't write anymore at all."
|
|
14
|
-
*
|
|
15
|
-
* Neutral touches (`ontologyOf` → null: root config, Skills, root-level
|
|
16
|
-
* Knowledge, non-KB) never record and never block. See
|
|
17
|
-
* `docs/rp4-ontology-boundary-plan-v2.md`.
|
|
18
|
-
*
|
|
19
|
-
* `SessionOntologyService` (Postgres-backed) persists the touched set and calls
|
|
20
|
-
* these helpers; this module keeps the rule itself testable in isolation.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import type { Ontology } from '@bevel-software/platform-shared';
|
|
24
|
-
|
|
25
|
-
/** Outcome of a write check against the session's touched-ontology set. */
|
|
26
|
-
export type WriteDecision =
|
|
27
|
-
| { allow: true }
|
|
28
|
-
| { allow: false; reason: 'multi-ontology' | 'different-ontology'; touched: string[]; attempted: string };
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* The set of NAMED ontologies a session has touched after this operation. A
|
|
32
|
-
* neutral operation (`ontology === null`) adds nothing. Pure — returns a new
|
|
33
|
-
* set, never mutates the input.
|
|
34
|
-
*/
|
|
35
|
-
export function recordTouch(touched: ReadonlySet<string>, ontology: Ontology): Set<string> {
|
|
36
|
-
const next = new Set(touched);
|
|
37
|
-
if (ontology !== null) next.add(ontology);
|
|
38
|
-
return next;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Decide whether a WRITE to `attempted` is allowed given the ontologies the
|
|
43
|
-
* session has already touched (NOT counting this write yet).
|
|
44
|
-
*
|
|
45
|
-
* - A write to a neutral path (`attempted === null`) is always allowed.
|
|
46
|
-
* - If the session has already touched two or more named ontologies, the
|
|
47
|
-
* write is blocked (`multi-ontology`) — reading across ontologies poisoned
|
|
48
|
-
* the session's write ability.
|
|
49
|
-
* - If the session has touched exactly one named ontology and it differs from
|
|
50
|
-
* the write target, the write is blocked (`different-ontology`).
|
|
51
|
-
* - If the session is unpinned (touched none) or its single touched ontology
|
|
52
|
-
* equals the target, the write is allowed.
|
|
53
|
-
*/
|
|
54
|
-
export function decideWrite(touched: ReadonlySet<string>, attempted: Ontology): WriteDecision {
|
|
55
|
-
if (attempted === null) return { allow: true };
|
|
56
|
-
|
|
57
|
-
// The set of named ontologies the session would span if this write lands.
|
|
58
|
-
const spanned = new Set(touched);
|
|
59
|
-
spanned.add(attempted);
|
|
60
|
-
|
|
61
|
-
if (spanned.size <= 1) return { allow: true };
|
|
62
|
-
|
|
63
|
-
// spanned.size >= 2 — the write would put the session across a boundary.
|
|
64
|
-
const touchedList = [...touched].sort();
|
|
65
|
-
if (touched.size >= 2) {
|
|
66
|
-
return { allow: false, reason: 'multi-ontology', touched: touchedList, attempted };
|
|
67
|
-
}
|
|
68
|
-
// Exactly one prior ontology, and it isn't the write target.
|
|
69
|
-
return { allow: false, reason: 'different-ontology', touched: touchedList, attempted };
|
|
70
|
-
}
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ontology-session boundary — the touched-set store (Postgres + cache).
|
|
3
|
-
*
|
|
4
|
-
* Each session accumulates the SET of named ontologies it has touched. The gate
|
|
5
|
-
* calls `checkOperation` for every gated agent tool call:
|
|
6
|
-
*
|
|
7
|
-
* READ (isWrite=false) → always allowed; RECORD the touched ontology so it
|
|
8
|
-
* can poison later writes.
|
|
9
|
-
* WRITE (isWrite=true) → allowed only while the session is confined to a
|
|
10
|
-
* single named ontology equal to the target; once the
|
|
11
|
-
* session has touched ≥2 ontologies, every write is
|
|
12
|
-
* blocked. An allowed write also records its ontology.
|
|
13
|
-
*
|
|
14
|
-
* Neutral operations (`ontologyOf` → null) never record and never block.
|
|
15
|
-
*
|
|
16
|
-
* ┌── checkOperation(sessionId, wsPath, isWrite) ───────────────────────────┐
|
|
17
|
-
* │ ont = ontologyOf(wsPath) │
|
|
18
|
-
* │ ont === null → { allow:true } (neutral; no record) │
|
|
19
|
-
* │ touched = loadTouched(sessionId) (cache, else SELECT) │
|
|
20
|
-
* │ WRITE → decideWrite(touched, ont); if blocked, return block (no record) │
|
|
21
|
-
* │ record(sessionId, ont) (idempotent INSERT; update cache) │
|
|
22
|
-
* │ return { allow:true } │
|
|
23
|
-
* └──────────────────────────────────────────────────────────────────────────┘
|
|
24
|
-
*
|
|
25
|
-
* Postgres is the source of truth so the boundary survives a backend restart.
|
|
26
|
-
* The cache holds each session's touched set; a miss re-reads PG. See
|
|
27
|
-
* `docs/rp4-ontology-boundary-plan-v2.md`.
|
|
28
|
-
*
|
|
29
|
-
* Eviction: `delete(sessionId)` at run end is the PRIMARY reclamation;
|
|
30
|
-
* `sweepOlderThan` is the abandoned-run backstop; the MCP layer also calls
|
|
31
|
-
* `delete` from its `onEvict` hook.
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
import { eq, lt } from 'drizzle-orm';
|
|
35
|
-
import { ontologyOf } from '../../shared/kb-layout.js';
|
|
36
|
-
import type { KbContext } from '../../shared/kb-context.js';
|
|
37
|
-
|
|
38
|
-
import type { Database } from '../database/connection.js';
|
|
39
|
-
import { sessionOntologyTouches } from '../database/schema.js';
|
|
40
|
-
import { recordTouch, decideWrite, type WriteDecision } from './session-ontology.policy.js';
|
|
41
|
-
import { WorkspaceMutex } from '../kb-fs/mutex.js';
|
|
42
|
-
|
|
43
|
-
export type OperationDecision =
|
|
44
|
-
| { allow: true; touched: string[] }
|
|
45
|
-
| { allow: false; reason: 'multi-ontology' | 'different-ontology'; touched: string[]; attempted: string };
|
|
46
|
-
|
|
47
|
-
export interface ISessionOntologyService {
|
|
48
|
-
/**
|
|
49
|
-
* Evaluate and record a gated agent operation. Reads always allow (and
|
|
50
|
-
* record); writes allow only while confined to a single ontology. Neutral
|
|
51
|
-
* paths always allow and record nothing.
|
|
52
|
-
*/
|
|
53
|
-
checkOperation(sessionId: string, wsPath: string, isWrite: boolean, kbDirName?: string): Promise<OperationDecision>;
|
|
54
|
-
/** Evaluate/record against an already-resolved ontology (used by graph tools). */
|
|
55
|
-
checkOntology(sessionId: string, ontology: string | null, isWrite: boolean): Promise<OperationDecision>;
|
|
56
|
-
/** The set of ontologies a session has touched. */
|
|
57
|
-
getTouched(sessionId: string): Promise<string[]>;
|
|
58
|
-
/** Reclaim a session's touched set (run end / MCP onEvict). */
|
|
59
|
-
delete(sessionId: string): Promise<void>;
|
|
60
|
-
/** Backstop: drop touch rows older than `cutoff` (abandoned runs). */
|
|
61
|
-
sweepOlderThan(cutoff: Date): Promise<number>;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export class SessionOntologyService implements ISessionOntologyService {
|
|
65
|
-
/**
|
|
66
|
-
* Write-through cache of each session's touched-ontology set. A present entry
|
|
67
|
-
* is authoritative for the session (we only ever add to it in-process); a
|
|
68
|
-
* miss re-reads Postgres. Keyed by the agent thread / sessionId.
|
|
69
|
-
*/
|
|
70
|
-
private readonly cache = new Map<string, Set<string>>();
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Serializes each session's read→decide→record path. Without it, two
|
|
74
|
-
* concurrent same-session writes (parallel tool calls in one turn) could both
|
|
75
|
-
* observe a singleton/empty touched set and both be allowed to different
|
|
76
|
-
* ontologies before either INSERT lands — defeating the boundary. Different
|
|
77
|
-
* sessions never contend.
|
|
78
|
-
*/
|
|
79
|
-
private readonly locks = new WorkspaceMutex();
|
|
80
|
-
|
|
81
|
-
constructor(
|
|
82
|
-
private readonly db: Database,
|
|
83
|
-
/** Names the knowledge-base root a path is classified under; read per call. */
|
|
84
|
-
private readonly kb: Pick<KbContext, 'layout'>,
|
|
85
|
-
) {}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Resolve `wsPath` to its ontology and evaluate/record the operation. Thin
|
|
89
|
-
* wrapper over {@link checkOntology} for the common path-based call site.
|
|
90
|
-
*/
|
|
91
|
-
async checkOperation(
|
|
92
|
-
sessionId: string,
|
|
93
|
-
wsPath: string,
|
|
94
|
-
isWrite: boolean,
|
|
95
|
-
kbDirName?: string,
|
|
96
|
-
): Promise<OperationDecision> {
|
|
97
|
-
return this.checkOntology(sessionId, ontologyOf(wsPath, this.kb.layout, kbDirName), isWrite);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Evaluate (and, when allowed, record) an operation against an
|
|
102
|
-
* already-resolved ontology. Neutral ops always allow and record nothing;
|
|
103
|
-
* reads always allow and record; writes allow only while the session stays
|
|
104
|
-
* confined to the single target ontology (see {@link decideWrite}). A blocked
|
|
105
|
-
* write records nothing.
|
|
106
|
-
*/
|
|
107
|
-
async checkOntology(sessionId: string, ontology: string | null, isWrite: boolean): Promise<OperationDecision> {
|
|
108
|
-
// Neutral operation: always allowed, records nothing. No state mutation, so
|
|
109
|
-
// it needs no per-session serialization.
|
|
110
|
-
if (ontology === null) {
|
|
111
|
-
const touched = await this.loadTouched(sessionId);
|
|
112
|
-
return { allow: true, touched: [...touched].sort() };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Serialize the decide+record path per session so concurrent same-session
|
|
116
|
-
// operations can't both pass the boundary on a stale touched set.
|
|
117
|
-
return this.locks.run(sessionId, async () => {
|
|
118
|
-
const touched = await this.loadTouched(sessionId);
|
|
119
|
-
|
|
120
|
-
if (isWrite) {
|
|
121
|
-
const decision: WriteDecision = decideWrite(touched, ontology);
|
|
122
|
-
if (!decision.allow) {
|
|
123
|
-
return {
|
|
124
|
-
allow: false,
|
|
125
|
-
reason: decision.reason,
|
|
126
|
-
touched: decision.touched,
|
|
127
|
-
attempted: decision.attempted,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Allowed (read, or a write confined to one ontology): record the touch.
|
|
133
|
-
await this.record(sessionId, ontology, touched);
|
|
134
|
-
return { allow: true, touched: [...recordTouch(touched, ontology)].sort() };
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** The sorted set of named ontologies this session has touched so far. */
|
|
139
|
-
async getTouched(sessionId: string): Promise<string[]> {
|
|
140
|
-
return [...(await this.loadTouched(sessionId))].sort();
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/** Reclaim a session's touched set from both the cache and Postgres (run end / MCP onEvict). */
|
|
144
|
-
async delete(sessionId: string): Promise<void> {
|
|
145
|
-
this.cache.delete(sessionId);
|
|
146
|
-
await this.db.delete(sessionOntologyTouches).where(eq(sessionOntologyTouches.sessionId, sessionId));
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/** Backstop sweep: drop touch rows older than `cutoff` (abandoned runs) and return how many were removed. */
|
|
150
|
-
async sweepOlderThan(cutoff: Date): Promise<number> {
|
|
151
|
-
const removed = await this.db
|
|
152
|
-
.delete(sessionOntologyTouches)
|
|
153
|
-
.where(lt(sessionOntologyTouches.touchedAt, cutoff))
|
|
154
|
-
.returning({ sessionId: sessionOntologyTouches.sessionId });
|
|
155
|
-
// A swept session may have other (newer) rows; drop its cache entry so the
|
|
156
|
-
// next op re-reads the authoritative remaining set from Postgres.
|
|
157
|
-
for (const r of removed) this.cache.delete(r.sessionId);
|
|
158
|
-
return removed.length;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/** Load a session's touched set from cache, else from Postgres (and cache it). */
|
|
162
|
-
private async loadTouched(sessionId: string): Promise<Set<string>> {
|
|
163
|
-
const cached = this.cache.get(sessionId);
|
|
164
|
-
if (cached) return cached;
|
|
165
|
-
const rows = await this.db
|
|
166
|
-
.select({ ontology: sessionOntologyTouches.ontology })
|
|
167
|
-
.from(sessionOntologyTouches)
|
|
168
|
-
.where(eq(sessionOntologyTouches.sessionId, sessionId));
|
|
169
|
-
const set = new Set(rows.map((r) => r.ontology));
|
|
170
|
-
this.cache.set(sessionId, set);
|
|
171
|
-
return set;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/** Record a touched ontology (idempotent) and update the cache. */
|
|
175
|
-
private async record(sessionId: string, ontology: string, current: Set<string>): Promise<void> {
|
|
176
|
-
if (current.has(ontology)) return; // already recorded; no DB write needed
|
|
177
|
-
await this.db
|
|
178
|
-
.insert(sessionOntologyTouches)
|
|
179
|
-
.values({ sessionId, ontology })
|
|
180
|
-
.onConflictDoNothing({ target: [sessionOntologyTouches.sessionId, sessionOntologyTouches.ontology] });
|
|
181
|
-
current.add(ontology);
|
|
182
|
-
}
|
|
183
|
-
}
|