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